react-native-alpha-video-gpu 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +949 -2
- package/THIRD_PARTY_NOTICES.md +14 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/cpp/CMakeLists.txt +41 -0
- package/android/src/main/cpp/alphavideogpu_jni.cpp +1168 -0
- package/android/src/main/cpp/astc_decomp.cpp +1550 -0
- package/android/src/main/cpp/astc_decomp.h +41 -0
- package/android/src/main/cpp/source_jni.cpp +242 -0
- package/android/src/main/cpp/source_jni.h +13 -0
- package/android/src/main/java/dev/mediacache/alphavideo/gpu/AlphaVideoGpuNative.java +43 -0
- package/android/src/main/java/dev/mediacache/alphavideo/gpu/AlphaVideoGpuReader.java +209 -0
- package/android/src/main/java/dev/mediacache/alphavideo/gpu/AlphaVideoGpuSource.java +328 -0
- package/android/src/main/java/dev/mediacache/alphavideo/gpu/AlphaVideoGpuView.java +2126 -0
- package/android/src/main/java/dev/mediacache/alphavideo/gpu/Fetcher.java +37 -0
- package/android/src/main/java/dev/mediacache/alphavideo/gpu/HttpFetcher.java +156 -0
- package/android/src/main/java/dev/mediacache/alphavideo/gpu/Ktx2Metadata.java +78 -0
- package/android/src/main/java/dev/mediacache/alphavideo/gpu/RangeSourceLink.java +54 -0
- package/android/src/main/java/dev/mediacache/alphavideo/gpu/ZstdSeekTable.java +120 -0
- package/android/src/rangesource/java/dev/mediacache/alphavideo/gpu/AppDataSource.java +50 -0
- package/ios/Sources/AlphaVideoGPU/AlphaVideoGpuSource.h +63 -0
- package/ios/Sources/AlphaVideoGPU/AlphaVideoGpuSource.mm +522 -0
- package/ios/Sources/AlphaVideoGPU/AlphaVideoGpuView.mm +2722 -0
- package/ios/Sources/AlphaVideoGPU/astc_decomp.cpp +1550 -0
- package/ios/Sources/AlphaVideoGPU/astc_decomp.h +41 -0
- package/ios/Sources/AlphaVideoGPU/include/AlphaVideoGPU.h +110 -0
- package/ios/Sources/AlphaVideoGPU/zstd_seek.h +250 -0
- package/package.json +77 -4
- package/react-native/android/build.gradle +92 -0
- package/react-native/android/src/main/AndroidManifest.xml +9 -0
- package/react-native/android/src/main/java/dev/mediacache/alphavideo/gpu/reactnative/AlphaVideoGpuPackage.java +26 -0
- package/react-native/android/src/main/java/dev/mediacache/alphavideo/gpu/reactnative/AlphaVideoGpuViewManager.java +187 -0
- package/react-native/android/src/main/java/dev/mediacache/alphavideo/gpu/reactnative/PlayerEventForwarder.java +107 -0
- package/react-native/android/src/newarch/java/dev/mediacache/alphavideo/gpu/reactnative/AlphaVideoGpuViewManagerSpec.java +23 -0
- package/react-native/android/src/norangesource/java/dev/mediacache/alphavideo/gpu/AppDataSource.java +15 -0
- package/react-native/android/src/oldarch/java/dev/mediacache/alphavideo/gpu/reactnative/AlphaVideoGpuViewManagerSpec.java +56 -0
- package/react-native/ios/RNAlphaVideoGpuViewComponentView.mm +176 -0
- package/react-native/ios/RNAlphaVideoGpuViewManager.mm +175 -0
- package/react-native/src/AlphaVideoGpuView.js +2 -0
- package/react-native/src/AlphaVideoGpuView.web.tsx +264 -0
- package/react-native/src/AlphaVideoGpuViewNativeComponent.ts +128 -0
- package/react-native/src/AlphaVideoGpuViewNativeComponent.web.ts +2 -0
- package/react-native/src/index.d.ts +95 -0
- package/react-native/src/index.js +162 -0
- package/react-native-alpha-video-gpu.podspec +37 -0
- package/react-native.config.js +12 -0
- package/third_party/licenses/Apache-2.0.txt +208 -0
- package/third_party/range-source/LICENSE +21 -0
- package/third_party/range-source/README.md +10 -0
- package/third_party/range-source/include/range_source.h +395 -0
- package/third_party/range-source/include/range_source_download.h +309 -0
- package/third_party/zstd/LICENSE +30 -0
- package/third_party/zstd/zstd.c +38717 -0
- package/third_party/zstd/zstd.h +2450 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlphaVideoGpuSource.mm
|
|
3
|
+
* Reading URLs and data sources: the built-in URLSession fetcher, and the reader it feeds, the
|
|
4
|
+
* range-source add-on's through its C table, or without the add-on the basic input, a download
|
|
5
|
+
* into a temporary file. A data source is read by the add-on, which the player finds at runtime.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#import "AlphaVideoGpuSource.h"
|
|
9
|
+
|
|
10
|
+
// range-source's C interface and the basic input (third_party/range-source/include), with their
|
|
11
|
+
// C++ wrappers in a namespace of this library's own, as each player that includes them does
|
|
12
|
+
#define RANGE_SOURCE_CLIENT_NAMESPACE avg_rs
|
|
13
|
+
#include "range_source.h"
|
|
14
|
+
#include "range_source_download.h"
|
|
15
|
+
|
|
16
|
+
#include <condition_variable>
|
|
17
|
+
#include <cstddef>
|
|
18
|
+
#include <functional>
|
|
19
|
+
#include <mutex>
|
|
20
|
+
#include <string>
|
|
21
|
+
|
|
22
|
+
namespace {
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Bytes handed from callbacks on any thread to the reader's fetch thread: a response, then data,
|
|
26
|
+
* then the end or an error. With flow control, the producer is paused while more than kHighBytes
|
|
27
|
+
* wait and resumed under kLowBytes.
|
|
28
|
+
*/
|
|
29
|
+
class Pipe {
|
|
30
|
+
public:
|
|
31
|
+
static constexpr size_t kHighBytes = 1024 * 1024;
|
|
32
|
+
static constexpr size_t kLowBytes = 256 * 1024;
|
|
33
|
+
|
|
34
|
+
/** Called under the pipe's lock with true to pause the producer, false to resume it. */
|
|
35
|
+
void setFlowControl(std::function<void(bool)> pause) {
|
|
36
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
37
|
+
pause_ = std::move(pause);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** `skip`: bytes of the data to drop first, for a server that answered from 0. */
|
|
41
|
+
void respond(const avg_rs::SourceInfo& info, int64_t skip = 0) {
|
|
42
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
43
|
+
if (responded_ || done_) return;
|
|
44
|
+
responded_ = true;
|
|
45
|
+
info_ = info;
|
|
46
|
+
skip_ = skip;
|
|
47
|
+
changed_.notify_all();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
void push(const uint8_t* data, size_t size) {
|
|
51
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
52
|
+
if (done_ || !responded_) return;
|
|
53
|
+
if (skip_ > 0) {
|
|
54
|
+
size_t dropped = static_cast<size_t>(std::min<int64_t>(skip_, static_cast<int64_t>(size)));
|
|
55
|
+
skip_ -= static_cast<int64_t>(dropped);
|
|
56
|
+
data += dropped;
|
|
57
|
+
size -= dropped;
|
|
58
|
+
}
|
|
59
|
+
if (size == 0) return;
|
|
60
|
+
buffer_.append(reinterpret_cast<const char*>(data), size);
|
|
61
|
+
if (pause_ && !paused_ && bufferedLocked() > kHighBytes) {
|
|
62
|
+
paused_ = true;
|
|
63
|
+
pause_(true);
|
|
64
|
+
}
|
|
65
|
+
changed_.notify_all();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** An empty error is the end of the data. Only the first call counts. */
|
|
69
|
+
void finish(const std::string& error) {
|
|
70
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
71
|
+
if (done_) return;
|
|
72
|
+
done_ = true;
|
|
73
|
+
error_ = error;
|
|
74
|
+
changed_.notify_all();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void cancel() { finish("Cancelled"); }
|
|
78
|
+
|
|
79
|
+
bool waitResponse(avg_rs::SourceInfo& info, std::string& error) {
|
|
80
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
81
|
+
changed_.wait(lock, [&] { return responded_ || done_; });
|
|
82
|
+
if (!responded_) {
|
|
83
|
+
error = error_.empty() ? "The source ended without a response" : error_;
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
info = info_;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Bytes read, 0 at the end, or -1 with `error` set; the data before an error comes first. */
|
|
91
|
+
int64_t read(uint8_t* out, int64_t size, std::string& error) {
|
|
92
|
+
std::unique_lock<std::mutex> lock(mutex_);
|
|
93
|
+
changed_.wait(lock, [&] { return bufferedLocked() > 0 || done_; });
|
|
94
|
+
size_t available = bufferedLocked();
|
|
95
|
+
if (available > 0) {
|
|
96
|
+
size_t n = std::min(available, static_cast<size_t>(size));
|
|
97
|
+
memcpy(out, buffer_.data() + offset_, n);
|
|
98
|
+
offset_ += n;
|
|
99
|
+
if (offset_ == buffer_.size()) {
|
|
100
|
+
buffer_.clear();
|
|
101
|
+
offset_ = 0;
|
|
102
|
+
} else if (offset_ > kHighBytes) {
|
|
103
|
+
buffer_.erase(0, offset_);
|
|
104
|
+
offset_ = 0;
|
|
105
|
+
}
|
|
106
|
+
if (paused_ && bufferedLocked() < kLowBytes) {
|
|
107
|
+
paused_ = false;
|
|
108
|
+
pause_(false);
|
|
109
|
+
}
|
|
110
|
+
return static_cast<int64_t>(n);
|
|
111
|
+
}
|
|
112
|
+
if (!error_.empty()) {
|
|
113
|
+
error = error_;
|
|
114
|
+
return -1;
|
|
115
|
+
}
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private:
|
|
120
|
+
size_t bufferedLocked() const { return buffer_.size() - offset_; }
|
|
121
|
+
|
|
122
|
+
std::mutex mutex_;
|
|
123
|
+
std::condition_variable changed_;
|
|
124
|
+
std::function<void(bool)> pause_;
|
|
125
|
+
bool paused_ = false;
|
|
126
|
+
bool responded_ = false;
|
|
127
|
+
bool done_ = false;
|
|
128
|
+
avg_rs::SourceInfo info_;
|
|
129
|
+
int64_t skip_ = 0;
|
|
130
|
+
std::string buffer_;
|
|
131
|
+
size_t offset_ = 0;
|
|
132
|
+
std::string error_;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
std::string stdString(NSString* text) {
|
|
136
|
+
return text ? std::string(text.UTF8String) : std::string();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
NSString* nsString(const std::string& text) {
|
|
140
|
+
return [NSString stringWithUTF8String:text.c_str()] ?: @"";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
std::string describe(NSError* error) {
|
|
144
|
+
return stdString(error.localizedDescription ?: @"The read failed");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
} // namespace
|
|
148
|
+
|
|
149
|
+
#pragma mark - The built-in URLSession fetcher
|
|
150
|
+
|
|
151
|
+
/** One task's pipe, and where its read starts. */
|
|
152
|
+
@interface AVGTaskState : NSObject {
|
|
153
|
+
@public
|
|
154
|
+
std::shared_ptr<Pipe> pipe;
|
|
155
|
+
int64_t position;
|
|
156
|
+
}
|
|
157
|
+
@end
|
|
158
|
+
|
|
159
|
+
@implementation AVGTaskState
|
|
160
|
+
@end
|
|
161
|
+
|
|
162
|
+
/** Routes a session's task callbacks to each task's pipe. Callbacks run on the session's queue. */
|
|
163
|
+
@interface AVGSessionDelegate : NSObject <NSURLSessionDataDelegate>
|
|
164
|
+
- (void)track:(NSURLSessionTask *)task state:(AVGTaskState *)state;
|
|
165
|
+
@end
|
|
166
|
+
|
|
167
|
+
@implementation AVGSessionDelegate {
|
|
168
|
+
// @synchronized (self)
|
|
169
|
+
NSMutableDictionary<NSNumber *, AVGTaskState *> *_tasks;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
- (instancetype)init {
|
|
173
|
+
if ((self = [super init])) _tasks = [NSMutableDictionary dictionary];
|
|
174
|
+
return self;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
- (void)track:(NSURLSessionTask *)task state:(AVGTaskState *)state {
|
|
178
|
+
@synchronized (self) {
|
|
179
|
+
_tasks[@(task.taskIdentifier)] = state;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
- (nullable AVGTaskState *)stateOf:(NSURLSessionTask *)task remove:(BOOL)remove {
|
|
184
|
+
@synchronized (self) {
|
|
185
|
+
AVGTaskState *state = _tasks[@(task.taskIdentifier)];
|
|
186
|
+
if (remove) [_tasks removeObjectForKey:@(task.taskIdentifier)];
|
|
187
|
+
return state;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static int64_t AVGTotalFromContentRange(NSString *contentRange, int64_t *start) {
|
|
192
|
+
// "bytes 100-199/1000", "bytes 100-199/*" or, answering 416, "bytes */1000"
|
|
193
|
+
NSScanner *scanner = [NSScanner scannerWithString:contentRange ?: @""];
|
|
194
|
+
long long first = -1, total = -1;
|
|
195
|
+
if (![scanner scanString:@"bytes" intoString:nil]) return -1;
|
|
196
|
+
if (![scanner scanString:@"*" intoString:nil]) {
|
|
197
|
+
if (![scanner scanLongLong:&first]) return -1;
|
|
198
|
+
[scanner scanUpToString:@"/" intoString:nil];
|
|
199
|
+
}
|
|
200
|
+
if (start) *start = first;
|
|
201
|
+
if (![scanner scanString:@"/" intoString:nil] || ![scanner scanLongLong:&total]) return -1;
|
|
202
|
+
return total;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
- (void)URLSession:(NSURLSession *)session
|
|
206
|
+
dataTask:(NSURLSessionDataTask *)dataTask
|
|
207
|
+
didReceiveResponse:(NSURLResponse *)response
|
|
208
|
+
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
|
|
209
|
+
AVGTaskState *state = [self stateOf:dataTask remove:NO];
|
|
210
|
+
if (!state) {
|
|
211
|
+
completionHandler(NSURLSessionResponseCancel);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
NSHTTPURLResponse *http = [response isKindOfClass:[NSHTTPURLResponse class]] ? (NSHTTPURLResponse *)response : nil;
|
|
215
|
+
avg_rs::SourceInfo info;
|
|
216
|
+
info.etag = stdString([http valueForHTTPHeaderField:@"ETag"]);
|
|
217
|
+
info.lastModified = stdString([http valueForHTTPHeaderField:@"Last-Modified"]);
|
|
218
|
+
NSString *encoding = [http valueForHTTPHeaderField:@"Content-Encoding"];
|
|
219
|
+
if (encoding.length > 0 && [encoding caseInsensitiveCompare:@"identity"] != NSOrderedSame) {
|
|
220
|
+
// URLSession decodes it, and the offsets would be into the decoded bytes
|
|
221
|
+
state->pipe->finish(stdString([NSString stringWithFormat:@"%@ came compressed (%@)", response.URL, encoding]));
|
|
222
|
+
completionHandler(NSURLSessionResponseCancel);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
NSInteger status = http ? http.statusCode : 200;
|
|
226
|
+
NSString *contentRange = [http valueForHTTPHeaderField:@"Content-Range"];
|
|
227
|
+
if (status == 206) {
|
|
228
|
+
int64_t start = -1;
|
|
229
|
+
info.size = AVGTotalFromContentRange(contentRange, &start);
|
|
230
|
+
if (start != state->position) {
|
|
231
|
+
state->pipe->finish(stdString([NSString stringWithFormat:@"Asked %@ for bytes from %lld and got %@",
|
|
232
|
+
response.URL, (long long)state->position, contentRange]));
|
|
233
|
+
completionHandler(NSURLSessionResponseCancel);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
state->pipe->respond(info);
|
|
237
|
+
} else if (status == 200) {
|
|
238
|
+
// No ranges: the whole file, read past the bytes before the position
|
|
239
|
+
info.size = response.expectedContentLength >= 0 ? response.expectedContentLength : -1;
|
|
240
|
+
state->pipe->respond(info, state->position);
|
|
241
|
+
} else if (status == 416) {
|
|
242
|
+
// At or past the end: a read that ends at once
|
|
243
|
+
info.size = AVGTotalFromContentRange(contentRange, nullptr);
|
|
244
|
+
state->pipe->respond(info);
|
|
245
|
+
state->pipe->finish("");
|
|
246
|
+
completionHandler(NSURLSessionResponseCancel);
|
|
247
|
+
return;
|
|
248
|
+
} else {
|
|
249
|
+
state->pipe->finish(stdString([NSString stringWithFormat:@"HTTP %ld for %@", (long)status, response.URL]));
|
|
250
|
+
completionHandler(NSURLSessionResponseCancel);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
completionHandler(NSURLSessionResponseAllow);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
|
|
257
|
+
AVGTaskState *state = [self stateOf:dataTask remove:NO];
|
|
258
|
+
if (!state) return;
|
|
259
|
+
[data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange range, BOOL *stop) {
|
|
260
|
+
state->pipe->push(static_cast<const uint8_t *>(bytes), range.length);
|
|
261
|
+
}];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
|
|
265
|
+
AVGTaskState *state = [self stateOf:task remove:YES];
|
|
266
|
+
if (!state) return;
|
|
267
|
+
state->pipe->finish(error ? describe(error) : "");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
@end
|
|
271
|
+
|
|
272
|
+
namespace {
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Reads a URL with URLSession: a `Range: bytes=N-` request per open, kept open as the reader
|
|
276
|
+
* reads on, and suspended while the reader has a megabyte waiting.
|
|
277
|
+
*/
|
|
278
|
+
class URLSessionUpstream : public avg_rs::Upstream {
|
|
279
|
+
public:
|
|
280
|
+
URLSessionUpstream(NSURL* url, NSDictionary<NSString*, NSString*>* headers)
|
|
281
|
+
: url_(url), headers_([headers copy]), delegate_([AVGSessionDelegate new]) {
|
|
282
|
+
NSURLSessionConfiguration* configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
283
|
+
// The reader's store is the cache
|
|
284
|
+
configuration.URLCache = nil;
|
|
285
|
+
configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
|
286
|
+
configuration.timeoutIntervalForRequest = 30;
|
|
287
|
+
session_ = [NSURLSession sessionWithConfiguration:configuration delegate:delegate_ delegateQueue:nil];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
~URLSessionUpstream() override {
|
|
291
|
+
close();
|
|
292
|
+
// The session holds its delegate until invalidated
|
|
293
|
+
[session_ invalidateAndCancel];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
bool open(int64_t position, avg_rs::SourceInfo& info, std::string& error) override {
|
|
297
|
+
close();
|
|
298
|
+
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url_];
|
|
299
|
+
[headers_ enumerateKeysAndObjectsUsingBlock:^(NSString* name, NSString* value, BOOL* stop) {
|
|
300
|
+
[request setValue:value forHTTPHeaderField:name];
|
|
301
|
+
}];
|
|
302
|
+
// Offsets are into the file as stored, so no transparent gzip
|
|
303
|
+
[request setValue:@"identity" forHTTPHeaderField:@"Accept-Encoding"];
|
|
304
|
+
[request setValue:[NSString stringWithFormat:@"bytes=%lld-", (long long)position] forHTTPHeaderField:@"Range"];
|
|
305
|
+
NSURLSessionDataTask* task = [session_ dataTaskWithRequest:request];
|
|
306
|
+
|
|
307
|
+
auto pipe = std::make_shared<Pipe>();
|
|
308
|
+
pipe->setFlowControl([task](bool pause) {
|
|
309
|
+
if (pause) [task suspend];
|
|
310
|
+
else [task resume];
|
|
311
|
+
});
|
|
312
|
+
{
|
|
313
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
314
|
+
if (cancelled_) {
|
|
315
|
+
error = "Cancelled";
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
task_ = task;
|
|
319
|
+
pipe_ = pipe;
|
|
320
|
+
}
|
|
321
|
+
AVGTaskState* state = [AVGTaskState new];
|
|
322
|
+
state->pipe = pipe;
|
|
323
|
+
state->position = position;
|
|
324
|
+
[delegate_ track:task state:state];
|
|
325
|
+
[task resume];
|
|
326
|
+
return pipe->waitResponse(info, error);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
int64_t read(uint8_t* out, int64_t size, std::string& error) override {
|
|
330
|
+
std::shared_ptr<Pipe> pipe = currentPipe();
|
|
331
|
+
if (!pipe) {
|
|
332
|
+
error = "Not open";
|
|
333
|
+
return -1;
|
|
334
|
+
}
|
|
335
|
+
return pipe->read(out, size, error);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
void close() override {
|
|
339
|
+
NSURLSessionDataTask* task;
|
|
340
|
+
std::shared_ptr<Pipe> pipe;
|
|
341
|
+
{
|
|
342
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
343
|
+
task = task_;
|
|
344
|
+
pipe = std::move(pipe_);
|
|
345
|
+
task_ = nil;
|
|
346
|
+
}
|
|
347
|
+
if (pipe) pipe->cancel();
|
|
348
|
+
[task cancel];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
void cancel() override {
|
|
352
|
+
{
|
|
353
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
354
|
+
cancelled_ = true;
|
|
355
|
+
}
|
|
356
|
+
close();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private:
|
|
360
|
+
std::shared_ptr<Pipe> currentPipe() {
|
|
361
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
362
|
+
return pipe_;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
NSURL* const url_;
|
|
366
|
+
NSDictionary<NSString*, NSString*>* const headers_;
|
|
367
|
+
AVGSessionDelegate* const delegate_;
|
|
368
|
+
NSURLSession* session_;
|
|
369
|
+
std::mutex mutex_;
|
|
370
|
+
NSURLSessionDataTask* task_ = nil;
|
|
371
|
+
std::shared_ptr<Pipe> pipe_;
|
|
372
|
+
bool cancelled_ = false;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
} // namespace
|
|
376
|
+
|
|
377
|
+
#pragma mark - AVGSourceReader
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* The add-on's class, as the player uses it (range-source's RangeSource.h). The player doesn't
|
|
381
|
+
* link the add-on, so it finds the class by name.
|
|
382
|
+
*/
|
|
383
|
+
@protocol AVGRangeSourceAddOn
|
|
384
|
+
@property (class, nonatomic, readonly) const void *plugin;
|
|
385
|
+
+ (BOOL)getUpstream:(void *)upstream
|
|
386
|
+
forDataSourceNamed:(NSString *)name
|
|
387
|
+
URL:(NSURL *)url
|
|
388
|
+
headers:(NSDictionary<NSString *, NSString *> *)headers;
|
|
389
|
+
@end
|
|
390
|
+
|
|
391
|
+
static Class<AVGRangeSourceAddOn> AVGRangeSourceClass(void) {
|
|
392
|
+
Class addOn = NSClassFromString(@"RangeSource");
|
|
393
|
+
return [addOn respondsToSelector:@selector(plugin)]
|
|
394
|
+
&& [addOn respondsToSelector:@selector(getUpstream:forDataSourceNamed:URL:headers:)]
|
|
395
|
+
? addOn : Nil;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
static const RangeSourcePlugin *AVGRangeSourcePlugin(void) {
|
|
399
|
+
static const RangeSourcePlugin *plugin = [] {
|
|
400
|
+
Class<AVGRangeSourceAddOn> addOn = AVGRangeSourceClass();
|
|
401
|
+
if (!addOn) return static_cast<const RangeSourcePlugin *>(nullptr);
|
|
402
|
+
const RangeSourcePlugin *table = static_cast<const RangeSourcePlugin *>([addOn plugin]);
|
|
403
|
+
if (!table) return static_cast<const RangeSourcePlugin *>(nullptr);
|
|
404
|
+
if (table->abi != RANGE_SOURCE_ABI) {
|
|
405
|
+
NSLog(@"[AlphaVideoGPU] Not using range-source: its ABI is %d, and this player needs %d", table->abi,
|
|
406
|
+
RANGE_SOURCE_ABI);
|
|
407
|
+
return static_cast<const RangeSourcePlugin *>(nullptr);
|
|
408
|
+
}
|
|
409
|
+
if (table->structSize < offsetof(RangeSourcePlugin, releaseReader) + sizeof(table->releaseReader)) {
|
|
410
|
+
NSLog(@"[AlphaVideoGPU] Not using range-source: it is older than this player");
|
|
411
|
+
return static_cast<const RangeSourcePlugin *>(nullptr);
|
|
412
|
+
}
|
|
413
|
+
return table;
|
|
414
|
+
}();
|
|
415
|
+
return plugin;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
BOOL AVGRangeSourceAvailable(void) {
|
|
419
|
+
return AVGRangeSourcePlugin() != nullptr;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Caches/alpha-video-gpu on disk, or the memory store's name
|
|
423
|
+
static NSString *const AVGStoreName = @"alpha-video-gpu";
|
|
424
|
+
|
|
425
|
+
static std::shared_ptr<avg_rs::Store> AVGStore(const RangeSourcePlugin *plugin, NSString *storage, int64_t maxStorageBytes) {
|
|
426
|
+
if ([storage isEqualToString:@"disk"]) {
|
|
427
|
+
NSString *caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
|
|
428
|
+
return avg_rs::Store::disk(plugin, stdString([caches stringByAppendingPathComponent:AVGStoreName]), maxStorageBytes);
|
|
429
|
+
}
|
|
430
|
+
if ([storage isEqualToString:@"memory"]) return avg_rs::Store::memory(plugin, stdString(AVGStoreName), maxStorageBytes);
|
|
431
|
+
return nullptr;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
@implementation AVGSourceReader {
|
|
435
|
+
std::shared_ptr<avg_rs::Reader> _reader;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
+ (instancetype)openURL:(NSURL *)url
|
|
439
|
+
dataSource:(NSString *)dataSource
|
|
440
|
+
headers:(NSDictionary<NSString *, NSString *> *)headers
|
|
441
|
+
storage:(NSString *)storage
|
|
442
|
+
key:(NSString *)key
|
|
443
|
+
maxStorageBytes:(int64_t)maxStorageBytes
|
|
444
|
+
error:(NSString **)error {
|
|
445
|
+
const RangeSourcePlugin *plugin = AVGRangeSourcePlugin();
|
|
446
|
+
std::string openError;
|
|
447
|
+
std::shared_ptr<avg_rs::Reader> reader;
|
|
448
|
+
if (dataSource) {
|
|
449
|
+
RangeSourceUpstream upstream{};
|
|
450
|
+
if (!plugin || ![AVGRangeSourceClass() getUpstream:&upstream forDataSourceNamed:dataSource URL:url headers:headers]) {
|
|
451
|
+
if (error) *error = [NSString stringWithFormat:@"No data source is registered as \"%@\" (RangeDataSources)", dataSource];
|
|
452
|
+
return nil;
|
|
453
|
+
}
|
|
454
|
+
reader = avg_rs::RangeReader::open(plugin, upstream, AVGStore(plugin, storage, maxStorageBytes), stdString(key),
|
|
455
|
+
avg_rs::ReaderOptions(), openError);
|
|
456
|
+
} else if ([url.scheme isEqualToString:@"https"] || [url.scheme isEqualToString:@"http"]) {
|
|
457
|
+
auto upstream = std::make_unique<URLSessionUpstream>(url, headers);
|
|
458
|
+
reader = plugin
|
|
459
|
+
? std::shared_ptr<avg_rs::Reader>(avg_rs::RangeReader::open(plugin, std::move(upstream),
|
|
460
|
+
AVGStore(plugin, storage, maxStorageBytes),
|
|
461
|
+
stdString(key), avg_rs::ReaderOptions(), openError))
|
|
462
|
+
: avg_rs::DownloadReader::open(std::move(upstream), stdString(NSTemporaryDirectory()), openError);
|
|
463
|
+
} else {
|
|
464
|
+
if (error) *error = [NSString stringWithFormat:@"Not an http(s) URL: %@", url];
|
|
465
|
+
return nil;
|
|
466
|
+
}
|
|
467
|
+
if (!reader) {
|
|
468
|
+
if (error) *error = nsString(openError);
|
|
469
|
+
return nil;
|
|
470
|
+
}
|
|
471
|
+
AVGSourceReader *result = [AVGSourceReader new];
|
|
472
|
+
result->_reader = std::move(reader);
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
- (int64_t)size {
|
|
477
|
+
return _reader->size();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
- (NSString *)lastError {
|
|
481
|
+
std::string message = _reader->lastError();
|
|
482
|
+
return message.empty() ? @"reading failed" : nsString(message);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
- (NSData *)readAt:(int64_t)position length:(int64_t)length error:(NSString **)error {
|
|
486
|
+
NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)length];
|
|
487
|
+
uint8_t *out = static_cast<uint8_t *>(data.mutableBytes);
|
|
488
|
+
int64_t got = 0;
|
|
489
|
+
while (got < length) {
|
|
490
|
+
int64_t n = _reader->read(position + got, out + got, length - got);
|
|
491
|
+
if (n <= 0) {
|
|
492
|
+
if (error) {
|
|
493
|
+
*error = n == 0 ? [NSString stringWithFormat:@"The file ends at %lld, before %lld", (long long)(position + got),
|
|
494
|
+
(long long)(position + length)]
|
|
495
|
+
: [self lastError];
|
|
496
|
+
}
|
|
497
|
+
return nil;
|
|
498
|
+
}
|
|
499
|
+
got += n;
|
|
500
|
+
}
|
|
501
|
+
return data;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
- (NSData *)readAll:(NSString **)error {
|
|
505
|
+
int64_t size = _reader->size();
|
|
506
|
+
if (size < 0) {
|
|
507
|
+
if (error) *error = @"The file's size isn't known";
|
|
508
|
+
return nil;
|
|
509
|
+
}
|
|
510
|
+
return [self readAt:0 length:size error:error];
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
- (int64_t)tryReadAt:(int64_t)position into:(uint8_t *)buffer length:(size_t)length {
|
|
514
|
+
int64_t n = _reader->tryRead(position, buffer, static_cast<int64_t>(length));
|
|
515
|
+
return n == avg_rs::Reader::kPending ? AVGSourceReaderPending : n;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
- (void)close {
|
|
519
|
+
_reader->close();
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
@end
|