react-native-alpha-video-gpu 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +953 -2
  3. package/THIRD_PARTY_NOTICES.md +14 -0
  4. package/android/src/main/AndroidManifest.xml +9 -0
  5. package/android/src/main/cpp/CMakeLists.txt +41 -0
  6. package/android/src/main/cpp/alphavideogpu_jni.cpp +1168 -0
  7. package/android/src/main/cpp/astc_decomp.cpp +1550 -0
  8. package/android/src/main/cpp/astc_decomp.h +41 -0
  9. package/android/src/main/cpp/source_jni.cpp +242 -0
  10. package/android/src/main/cpp/source_jni.h +13 -0
  11. package/android/src/main/java/dev/mediacache/alphavideo/gpu/AlphaVideoGpuNative.java +43 -0
  12. package/android/src/main/java/dev/mediacache/alphavideo/gpu/AlphaVideoGpuReader.java +209 -0
  13. package/android/src/main/java/dev/mediacache/alphavideo/gpu/AlphaVideoGpuSource.java +328 -0
  14. package/android/src/main/java/dev/mediacache/alphavideo/gpu/AlphaVideoGpuView.java +2126 -0
  15. package/android/src/main/java/dev/mediacache/alphavideo/gpu/Fetcher.java +37 -0
  16. package/android/src/main/java/dev/mediacache/alphavideo/gpu/HttpFetcher.java +156 -0
  17. package/android/src/main/java/dev/mediacache/alphavideo/gpu/Ktx2Metadata.java +78 -0
  18. package/android/src/main/java/dev/mediacache/alphavideo/gpu/RangeSourceLink.java +54 -0
  19. package/android/src/main/java/dev/mediacache/alphavideo/gpu/ZstdSeekTable.java +120 -0
  20. package/android/src/rangesource/java/dev/mediacache/alphavideo/gpu/AppDataSource.java +50 -0
  21. package/ios/Sources/AlphaVideoGPU/AlphaVideoGpuSource.h +63 -0
  22. package/ios/Sources/AlphaVideoGPU/AlphaVideoGpuSource.mm +522 -0
  23. package/ios/Sources/AlphaVideoGPU/AlphaVideoGpuView.mm +2722 -0
  24. package/ios/Sources/AlphaVideoGPU/astc_decomp.cpp +1550 -0
  25. package/ios/Sources/AlphaVideoGPU/astc_decomp.h +41 -0
  26. package/ios/Sources/AlphaVideoGPU/include/AlphaVideoGPU.h +110 -0
  27. package/ios/Sources/AlphaVideoGPU/zstd_seek.h +250 -0
  28. package/package.json +78 -4
  29. package/react-native/android/build.gradle +95 -0
  30. package/react-native/android/src/main/AndroidManifest.xml +9 -0
  31. package/react-native/android/src/main/java/dev/mediacache/alphavideo/gpu/reactnative/AlphaVideoGpuPackage.java +26 -0
  32. package/react-native/android/src/main/java/dev/mediacache/alphavideo/gpu/reactnative/AlphaVideoGpuViewManager.java +187 -0
  33. package/react-native/android/src/main/java/dev/mediacache/alphavideo/gpu/reactnative/PlayerEventForwarder.java +107 -0
  34. package/react-native/android/src/newarch/java/dev/mediacache/alphavideo/gpu/reactnative/AlphaVideoGpuViewManagerSpec.java +23 -0
  35. package/react-native/android/src/norangesource/java/dev/mediacache/alphavideo/gpu/AppDataSource.java +15 -0
  36. package/react-native/android/src/oldarch/java/dev/mediacache/alphavideo/gpu/reactnative/AlphaVideoGpuViewManagerSpec.java +56 -0
  37. package/react-native/ios/RNAlphaVideoGpuViewComponentView.mm +176 -0
  38. package/react-native/ios/RNAlphaVideoGpuViewManager.mm +175 -0
  39. package/react-native/src/AlphaVideoGpuView.js +2 -0
  40. package/react-native/src/AlphaVideoGpuView.web.tsx +296 -0
  41. package/react-native/src/AlphaVideoGpuViewNativeComponent.ts +128 -0
  42. package/react-native/src/AlphaVideoGpuViewNativeComponent.web.ts +2 -0
  43. package/react-native/src/index.d.ts +95 -0
  44. package/react-native/src/index.js +162 -0
  45. package/react-native-alpha-video-gpu.podspec +37 -0
  46. package/react-native.config.js +12 -0
  47. package/third_party/licenses/Apache-2.0.txt +208 -0
  48. package/third_party/range-source/LICENSE +21 -0
  49. package/third_party/range-source/README.md +10 -0
  50. package/third_party/range-source/include/range_source.h +395 -0
  51. package/third_party/range-source/include/range_source_download.h +309 -0
  52. package/third_party/zstd/LICENSE +30 -0
  53. package/third_party/zstd/zstd.c +38717 -0
  54. package/third_party/zstd/zstd.h +2450 -0
@@ -0,0 +1,328 @@
1
+ package dev.mediacache.alphavideo.gpu;
2
+
3
+ import android.content.Context;
4
+ import android.content.res.AssetFileDescriptor;
5
+ import android.content.res.Resources;
6
+ import android.net.Uri;
7
+ import android.os.ParcelFileDescriptor;
8
+
9
+ import java.io.File;
10
+ import java.io.FileInputStream;
11
+ import java.io.FileNotFoundException;
12
+ import java.io.IOException;
13
+ import java.io.InputStream;
14
+ import java.nio.ByteBuffer;
15
+ import java.nio.channels.FileChannel;
16
+ import java.util.concurrent.atomic.AtomicBoolean;
17
+ import java.util.regex.Pattern;
18
+
19
+ import androidx.annotation.Nullable;
20
+
21
+ /**
22
+ * What the player reads: a local file it can read in place (a file, an APK asset, a raw resource,
23
+ * or a content:// URI), or a range reader over a URL or an app's data source.
24
+ *
25
+ * Headers, ranges and whole files are read through {@link #openStream}, which works for every
26
+ * kind. Streaming playback of a local file memory-maps a descriptor range from {@link #openFd},
27
+ * which for assets and raw resources requires them to be stored uncompressed in the APK; a reader
28
+ * is streamed from {@link #reader} instead.
29
+ */
30
+ final class AlphaVideoGpuSource {
31
+
32
+ private enum Kind { FILE, ASSET, RAW, CONTENT, READER }
33
+
34
+ private static final String ASSET_SCHEME = "asset://";
35
+ // What Image.resolveAssetSource(require(...)) gives in a release build: a res/raw name
36
+ private static final Pattern RAW_RESOURCE_NAME = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*");
37
+
38
+ private final Kind kind;
39
+ private final String location;
40
+ @Nullable private final AlphaVideoGpuReader reader;
41
+ // Stops the reader's waits once the playback stops
42
+ @Nullable private final AtomicBoolean cancelled;
43
+
44
+ private AlphaVideoGpuSource(Kind kind, String location) {
45
+ this(kind, location, null, null);
46
+ }
47
+
48
+ private AlphaVideoGpuSource(Kind kind, String location, @Nullable AlphaVideoGpuReader reader,
49
+ @Nullable AtomicBoolean cancelled) {
50
+ this.kind = kind;
51
+ this.location = location;
52
+ this.reader = reader;
53
+ this.cancelled = cancelled;
54
+ }
55
+
56
+ static AlphaVideoGpuSource file(String path) {
57
+ return new AlphaVideoGpuSource(Kind.FILE, path);
58
+ }
59
+
60
+ /** Reads through {@code reader}; its reads give up once {@code cancelled} is set. */
61
+ static AlphaVideoGpuSource reader(AlphaVideoGpuReader reader, AtomicBoolean cancelled) {
62
+ return new AlphaVideoGpuSource(Kind.READER, reader.toString(), reader, cancelled);
63
+ }
64
+
65
+ /** The range reader of a URL or data source; null for a local file. */
66
+ @Nullable
67
+ AlphaVideoGpuReader reader() {
68
+ return reader;
69
+ }
70
+
71
+ /**
72
+ * Parses {@code file:///abs/path}, {@code /abs/path}, {@code asset://relative/path},
73
+ * {@code content://...}, or a bare res/raw resource name.
74
+ *
75
+ * @throws IllegalArgumentException for anything else
76
+ */
77
+ static AlphaVideoGpuSource parse(String uri) {
78
+ if (uri.startsWith(ASSET_SCHEME)) {
79
+ String path = Uri.decode(uri.substring(ASSET_SCHEME.length()));
80
+ while (path.startsWith("/")) path = path.substring(1);
81
+ if (path.isEmpty()) throw new IllegalArgumentException("Empty asset path: " + uri);
82
+ return new AlphaVideoGpuSource(Kind.ASSET, path);
83
+ }
84
+ if (uri.startsWith("content://")) {
85
+ return new AlphaVideoGpuSource(Kind.CONTENT, uri);
86
+ }
87
+ if (uri.startsWith("file://")) {
88
+ String path = Uri.parse(uri).getPath();
89
+ if (path == null || path.isEmpty()) throw new IllegalArgumentException("Bad file URI: " + uri);
90
+ return file(path);
91
+ }
92
+ if (uri.startsWith("/")) {
93
+ return file(uri);
94
+ }
95
+ if (RAW_RESOURCE_NAME.matcher(uri).matches()) {
96
+ return new AlphaVideoGpuSource(Kind.RAW, uri);
97
+ }
98
+ throw new IllegalArgumentException(
99
+ "Unsupported source '" + uri + "'. Use https://, file://, an absolute path, asset:// or content://");
100
+ }
101
+
102
+ /** The caller closes the descriptor; native code duplicates it if it needs to keep reading. */
103
+ AssetFileDescriptor openFd(Context context) throws IOException {
104
+ switch (kind) {
105
+ case READER:
106
+ throw new IOException(location + " is read by range, not from a descriptor");
107
+ case ASSET:
108
+ try {
109
+ return context.getAssets().openFd(location);
110
+ } catch (FileNotFoundException e) {
111
+ if (!assetExists(context)) throw new FileNotFoundException("Asset not found: " + location);
112
+ throw compressedInApk("Asset " + location);
113
+ }
114
+ case RAW: {
115
+ Resources resources = context.getResources();
116
+ try {
117
+ return resources.openRawResourceFd(rawResourceId(context));
118
+ } catch (Resources.NotFoundException e) {
119
+ throw compressedInApk("Resource res/raw/" + location);
120
+ }
121
+ }
122
+ case CONTENT: {
123
+ AssetFileDescriptor afd = context.getContentResolver()
124
+ .openAssetFileDescriptor(Uri.parse(location), "r");
125
+ if (afd == null) throw new FileNotFoundException("Cannot open " + location);
126
+ return afd;
127
+ }
128
+ default: {
129
+ ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
130
+ new File(location), ParcelFileDescriptor.MODE_READ_ONLY);
131
+ return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH);
132
+ }
133
+ }
134
+ }
135
+
136
+ InputStream openStream(Context context) throws IOException {
137
+ switch (kind) {
138
+ case READER:
139
+ return new ReaderInputStream(reader, cancelled);
140
+ case ASSET:
141
+ return context.getAssets().open(location);
142
+ case RAW:
143
+ return context.getResources().openRawResource(rawResourceId(context));
144
+ case CONTENT: {
145
+ InputStream in = context.getContentResolver().openInputStream(Uri.parse(location));
146
+ if (in == null) throw new FileNotFoundException("Cannot open " + location);
147
+ return in;
148
+ }
149
+ default:
150
+ return new FileInputStream(location);
151
+ }
152
+ }
153
+
154
+ /** Up to {@code count} bytes from the start of the file (fewer if the file is shorter). */
155
+ byte[] readHead(Context context, int count) throws IOException {
156
+ try (InputStream in = openStream(context)) {
157
+ byte[] head = new byte[count];
158
+ int total = 0;
159
+ int n;
160
+ while (total < count && (n = in.read(head, total, count - total)) != -1) {
161
+ total += n;
162
+ }
163
+ return total == count ? head : java.util.Arrays.copyOf(head, total);
164
+ }
165
+ }
166
+
167
+ /** {@code count} bytes starting at {@code offset}, or fewer if the file ends first. */
168
+ byte[] readRange(Context context, long offset, int count) throws IOException {
169
+ try (InputStream in = openStream(context)) {
170
+ long skipped = 0;
171
+ while (skipped < offset) {
172
+ long n = in.skip(offset - skipped);
173
+ if (n <= 0) {
174
+ if (in.read() == -1) return new byte[0];
175
+ n = 1;
176
+ }
177
+ skipped += n;
178
+ }
179
+ byte[] range = new byte[count];
180
+ int total = 0;
181
+ int n;
182
+ while (total < count && (n = in.read(range, total, count - total)) != -1) {
183
+ total += n;
184
+ }
185
+ return total == count ? range : java.util.Arrays.copyOf(range, total);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Memory-maps a local file; the mapping stays valid after the descriptor is closed. Assets and
191
+ * raw resources must be stored uncompressed in the APK.
192
+ */
193
+ ByteBuffer map(Context context) throws IOException {
194
+ try (AssetFileDescriptor afd = openFd(context);
195
+ FileInputStream in = new FileInputStream(afd.getFileDescriptor())) {
196
+ FileChannel channel = in.getChannel();
197
+ long start = afd.getStartOffset();
198
+ long length = afd.getLength() >= 0 ? afd.getLength() : channel.size() - start;
199
+ return channel.map(FileChannel.MapMode.READ_ONLY, start, length);
200
+ }
201
+ }
202
+
203
+ /**
204
+ * The whole file in a direct buffer, for uploads straight from it, never on the Java heap: a
205
+ * local file mapped where it can be, a reader's file read into one.
206
+ */
207
+ ByteBuffer readAllDirect(Context context) throws IOException {
208
+ if (reader == null) {
209
+ try {
210
+ return map(context);
211
+ } catch (IOException e) {
212
+ // An entry compressed in the APK can only be read as a stream
213
+ try (InputStream in = openStream(context)) {
214
+ return readDirect(in);
215
+ }
216
+ }
217
+ }
218
+ long size = reader.size();
219
+ if (size < 0 || size > Integer.MAX_VALUE) {
220
+ try (InputStream in = openStream(context)) {
221
+ return readDirect(in);
222
+ }
223
+ }
224
+ ByteBuffer out = ByteBuffer.allocateDirect((int) size);
225
+ byte[] chunk = new byte[256 * 1024];
226
+ for (long pos = 0; pos < size; ) {
227
+ int n = reader.readFully(pos, chunk, 0, (int) Math.min(chunk.length, size - pos), cancelled);
228
+ if (n == 0) throw new IOException(location + " ended at " + pos + " of " + size + " bytes");
229
+ out.put(chunk, 0, n);
230
+ pos += n;
231
+ }
232
+ out.rewind();
233
+ return out;
234
+ }
235
+
236
+ /** A stream of unknown length into a direct buffer exactly as long, grown by doubling. */
237
+ private static ByteBuffer readDirect(InputStream in) throws IOException {
238
+ ByteBuffer out = ByteBuffer.allocateDirect(1024 * 1024);
239
+ byte[] chunk = new byte[256 * 1024];
240
+ int n;
241
+ while ((n = in.read(chunk)) != -1) {
242
+ if (out.remaining() < n) {
243
+ long grown = Math.max(2L * out.capacity(), (long) out.position() + n);
244
+ if (grown > Integer.MAX_VALUE) throw new IOException("The file is over 2 GB");
245
+ ByteBuffer bigger = ByteBuffer.allocateDirect((int) grown);
246
+ out.flip();
247
+ bigger.put(out);
248
+ out = bigger;
249
+ }
250
+ out.put(chunk, 0, n);
251
+ }
252
+ out.flip();
253
+ // Native code reads the buffer's capacity as the file's length
254
+ return out.slice();
255
+ }
256
+
257
+ /** A reader's bytes in order from 0, as a stream; skipping moves the position without reading. */
258
+ private static final class ReaderInputStream extends InputStream {
259
+ private final AlphaVideoGpuReader reader;
260
+ private final AtomicBoolean cancelled;
261
+ private long position = 0;
262
+
263
+ ReaderInputStream(AlphaVideoGpuReader reader, AtomicBoolean cancelled) {
264
+ this.reader = reader;
265
+ this.cancelled = cancelled;
266
+ }
267
+
268
+ @Override
269
+ public int read() throws IOException {
270
+ byte[] one = new byte[1];
271
+ return read(one, 0, 1) == 1 ? one[0] & 0xFF : -1;
272
+ }
273
+
274
+ @Override
275
+ public int read(byte[] buffer, int offset, int length) throws IOException {
276
+ if (length == 0) return 0;
277
+ int n = reader.readFully(position, buffer, offset, length, cancelled);
278
+ if (n == 0) return -1;
279
+ position += n;
280
+ return n;
281
+ }
282
+
283
+ @Override
284
+ public long skip(long n) {
285
+ if (n <= 0) return 0;
286
+ long size = reader.size();
287
+ long skipped = size >= 0 ? Math.max(0, Math.min(n, size - position)) : n;
288
+ position += skipped;
289
+ return skipped;
290
+ }
291
+ }
292
+
293
+ private int rawResourceId(Context context) throws FileNotFoundException {
294
+ int id = context.getResources().getIdentifier(location, "raw", context.getPackageName());
295
+ if (id == 0) throw new FileNotFoundException("No res/raw resource named " + location);
296
+ return id;
297
+ }
298
+
299
+ private static IOException compressedInApk(String what) {
300
+ // openFd and openRawResourceFd only work on entries stored uncompressed in the APK.
301
+ return new IOException(what + " is compressed in the APK, so it can't be streamed in place; add its "
302
+ + "extension to android.androidResources.noCompress in the app's build.gradle");
303
+ }
304
+
305
+ private boolean assetExists(Context context) {
306
+ int slash = location.lastIndexOf('/');
307
+ String dir = slash < 0 ? "" : location.substring(0, slash);
308
+ String name = location.substring(slash + 1);
309
+ try {
310
+ String[] names = context.getAssets().list(dir);
311
+ if (names == null) return false;
312
+ for (String n : names) {
313
+ if (n.equals(name)) return true;
314
+ }
315
+ } catch (IOException ignored) {
316
+ }
317
+ return false;
318
+ }
319
+
320
+ @Override
321
+ public String toString() {
322
+ switch (kind) {
323
+ case ASSET: return ASSET_SCHEME + location;
324
+ case RAW: return "res/raw/" + location;
325
+ default: return location;
326
+ }
327
+ }
328
+ }