react-native-skia-webgpu-video 0.0.0-stage → 0.1.0-beta.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.
Files changed (236) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +303 -2
  3. package/android/BUILD_VALIDATION.md +62 -0
  4. package/android/CMakeLists.txt +99 -0
  5. package/android/build-validation.json +262 -0
  6. package/android/build.gradle +185 -0
  7. package/android/cpp/EncoderTargets.cpp +226 -0
  8. package/android/cpp/EncoderTargets.h +70 -0
  9. package/android/cpp/JNIHelpers.cpp +15 -0
  10. package/android/cpp/JNIHelpers.h +20 -0
  11. package/android/cpp/NativeEventDispatcher.cpp +59 -0
  12. package/android/cpp/NativeEventDispatcher.h +31 -0
  13. package/android/cpp/NativeHardwareBuffer.cpp +397 -0
  14. package/android/cpp/NativeHardwareBuffer.h +34 -0
  15. package/android/cpp/NativeRgbaBuffer.cpp +45 -0
  16. package/android/cpp/NativeRgbaBuffer.h +9 -0
  17. package/android/cpp/ReturnedVideoFrames.h +37 -0
  18. package/android/cpp/RgbaFrameLease.h +53 -0
  19. package/android/cpp/VideoCapabilities.cpp +87 -0
  20. package/android/cpp/VideoCapabilities.h +44 -0
  21. package/android/cpp/VideoComposition.cpp +170 -0
  22. package/android/cpp/VideoComposition.h +36 -0
  23. package/android/cpp/VideoCompositionFramesExtractor.cpp +82 -0
  24. package/android/cpp/VideoCompositionFramesExtractor.h +48 -0
  25. package/android/cpp/VideoCompositionFramesExtractorHostObject.cpp +230 -0
  26. package/android/cpp/VideoCompositionFramesExtractorHostObject.h +42 -0
  27. package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.cpp +133 -0
  28. package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.h +46 -0
  29. package/android/cpp/VideoEncoderHostObject.cpp +318 -0
  30. package/android/cpp/VideoEncoderHostObject.h +76 -0
  31. package/android/cpp/VideoFrame.cpp +293 -0
  32. package/android/cpp/VideoFrame.h +27 -0
  33. package/android/cpp/VideoPlayer.cpp +96 -0
  34. package/android/cpp/VideoPlayer.h +52 -0
  35. package/android/cpp/VideoPlayerHostObject.cpp +225 -0
  36. package/android/cpp/VideoPlayerHostObject.h +43 -0
  37. package/android/cpp/cpp-adapter.cpp +316 -0
  38. package/android/gradle.properties +5 -0
  39. package/android/instrumentation-inventory.json +178 -0
  40. package/android/proguard-rules.pro +4 -0
  41. package/android/src/main/AndroidManifest.xml +2 -0
  42. package/android/src/main/java/com/azzapp/rnskv/AudioCompositionExporter.java +537 -0
  43. package/android/src/main/java/com/azzapp/rnskv/AudioCompositionPlayer.java +321 -0
  44. package/android/src/main/java/com/azzapp/rnskv/BufferLifetime.java +55 -0
  45. package/android/src/main/java/com/azzapp/rnskv/DecodeRequests.java +100 -0
  46. package/android/src/main/java/com/azzapp/rnskv/DecoderWindow.java +55 -0
  47. package/android/src/main/java/com/azzapp/rnskv/EGLResourcesHolder.java +132 -0
  48. package/android/src/main/java/com/azzapp/rnskv/EGLUtils.java +225 -0
  49. package/android/src/main/java/com/azzapp/rnskv/FrameSize.java +50 -0
  50. package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +510 -0
  51. package/android/src/main/java/com/azzapp/rnskv/JNIHelpers.java +12 -0
  52. package/android/src/main/java/com/azzapp/rnskv/NativeEventDispatcher.java +25 -0
  53. package/android/src/main/java/com/azzapp/rnskv/NativeHardwareBuffer.java +62 -0
  54. package/android/src/main/java/com/azzapp/rnskv/NativeRgbaBuffer.java +35 -0
  55. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoModule.java +45 -0
  56. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoPackage.java +43 -0
  57. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoSpec.java +9 -0
  58. package/android/src/main/java/com/azzapp/rnskv/RgbaBufferAllocator.java +83 -0
  59. package/android/src/main/java/com/azzapp/rnskv/RgbaLayout.java +33 -0
  60. package/android/src/main/java/com/azzapp/rnskv/SurfaceOutputPolicy.java +13 -0
  61. package/android/src/main/java/com/azzapp/rnskv/TextureRenderer.java +150 -0
  62. package/android/src/main/java/com/azzapp/rnskv/TimeHelpers.java +11 -0
  63. package/android/src/main/java/com/azzapp/rnskv/VideoCapabilities.java +354 -0
  64. package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +147 -0
  65. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +772 -0
  66. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +354 -0
  67. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractorSync.java +241 -0
  68. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionItemDecoder.java +492 -0
  69. package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +639 -0
  70. package/android/src/main/java/com/azzapp/rnskv/VideoFrame.java +74 -0
  71. package/android/src/main/java/com/azzapp/rnskv/VideoPlayer.java +361 -0
  72. package/cpp/RNSVCheckedSizes.h +54 -0
  73. package/cpp/RNSVEventEmitter.cpp +199 -0
  74. package/cpp/RNSVEventEmitter.h +31 -0
  75. package/cpp/RNSVHostObject.cpp +79 -0
  76. package/cpp/RNSVHostObject.h +67 -0
  77. package/cpp/RNSVMemoryBudget.cpp +68 -0
  78. package/cpp/RNSVMemoryBudget.h +100 -0
  79. package/cpp/RNSVRuntimeLifecycleMonitor.cpp +88 -0
  80. package/cpp/RNSVRuntimeLifecycleMonitor.h +33 -0
  81. package/docs/ANDROID_DEVICE_306.md +95 -0
  82. package/docs/ANDROID_EXPORT_CACHE_306.md +48 -0
  83. package/docs/ANDROID_MEMORY_RETENTION_306.md +67 -0
  84. package/docs/BENCHMARKS.md +368 -0
  85. package/docs/IOS_DEVICE_TESTS.md +115 -0
  86. package/docs/IOS_SIMULATOR_TESTS.md +74 -0
  87. package/docs/MIGRATION.md +82 -0
  88. package/docs/NATIVE_MEMORY_INVESTIGATION_306.md +88 -0
  89. package/docs/PERFORMANCE_AB_IPHONE_303.md +196 -0
  90. package/docs/PERFORMANCE_AB_SIMULATOR.md +135 -0
  91. package/docs/PERFORMANCE_AB_SOAK_SIMULATOR.md +84 -0
  92. package/docs/PERFORMANCE_IPHONE_306_OPTIMIZATION.md +76 -0
  93. package/docs/PERFORMANCE_SIMULATOR.md +89 -0
  94. package/docs/PERFORMANCE_SIMULATOR_INITIAL.md +96 -0
  95. package/docs/PERFORMANCE_SOAK_IPHONE_303.md +41 -0
  96. package/docs/UPSTREAM_NOTES.md +188 -0
  97. package/docs/assets/banner.svg +26 -0
  98. package/examples/webgpu-overlay.ts +147 -0
  99. package/ios/AVAssetTrackUtils.h +24 -0
  100. package/ios/AudioCompositionUtils.h +27 -0
  101. package/ios/AudioCompositionUtils.mm +113 -0
  102. package/ios/DecoderWindow.h +52 -0
  103. package/ios/NativeResourceStats.h +32 -0
  104. package/ios/RNSVColorSpace.h +50 -0
  105. package/ios/RNSVJSIUtils.h +46 -0
  106. package/ios/RNSVScopedPixelBuffer.h +17 -0
  107. package/ios/RNSVVideoPlayer.h +46 -0
  108. package/ios/RNSVVideoPlayer.mm +410 -0
  109. package/ios/ReactNativeSkiaVideo.h +8 -0
  110. package/ios/ReactNativeSkiaVideo.mm +310 -0
  111. package/ios/SeekPolicy.h +27 -0
  112. package/ios/VideoComposition.h +148 -0
  113. package/ios/VideoCompositionFramesExtractorHostObject.h +105 -0
  114. package/ios/VideoCompositionFramesExtractorHostObject.mm +686 -0
  115. package/ios/VideoCompositionFramesExtractorSyncHostObject.h +37 -0
  116. package/ios/VideoCompositionFramesExtractorSyncHostObject.mm +168 -0
  117. package/ios/VideoCompositionItemDecoder.h +60 -0
  118. package/ios/VideoCompositionItemDecoder.mm +480 -0
  119. package/ios/VideoEncoderHostObject.h +83 -0
  120. package/ios/VideoEncoderHostObject.mm +706 -0
  121. package/ios/VideoFrame.h +66 -0
  122. package/ios/VideoFrame.mm +108 -0
  123. package/ios/VideoPlayerHostObject.h +52 -0
  124. package/ios/VideoPlayerHostObject.mm +332 -0
  125. package/lib/module/NativeReactNativeSkiaVideo.js +5 -0
  126. package/lib/module/NativeReactNativeSkiaVideo.js.map +1 -0
  127. package/lib/module/RNSkiaVideoModule.js +20 -0
  128. package/lib/module/RNSkiaVideoModule.js.map +1 -0
  129. package/lib/module/canvas.js +18 -0
  130. package/lib/module/canvas.js.map +1 -0
  131. package/lib/module/exportCache.js +22 -0
  132. package/lib/module/exportCache.js.map +1 -0
  133. package/lib/module/exportFrames.js +338 -0
  134. package/lib/module/exportFrames.js.map +1 -0
  135. package/lib/module/exportQueue.js +52 -0
  136. package/lib/module/exportQueue.js.map +1 -0
  137. package/lib/module/exportTarget.js +137 -0
  138. package/lib/module/exportTarget.js.map +1 -0
  139. package/lib/module/exportVideoComposition.js +492 -0
  140. package/lib/module/exportVideoComposition.js.map +1 -0
  141. package/lib/module/frameInterop.js +532 -0
  142. package/lib/module/frameInterop.js.map +1 -0
  143. package/lib/module/gpu.js +222 -0
  144. package/lib/module/gpu.js.map +1 -0
  145. package/lib/module/gpuDevice.js +54 -0
  146. package/lib/module/gpuDevice.js.map +1 -0
  147. package/lib/module/index.js +36 -0
  148. package/lib/module/index.js.map +1 -0
  149. package/lib/module/memory.js +38 -0
  150. package/lib/module/memory.js.map +1 -0
  151. package/lib/module/package.json +1 -0
  152. package/lib/module/previewLifecycle.js +52 -0
  153. package/lib/module/previewLifecycle.js.map +1 -0
  154. package/lib/module/runtimeYield.js +10 -0
  155. package/lib/module/runtimeYield.js.map +1 -0
  156. package/lib/module/types.js +4 -0
  157. package/lib/module/types.js.map +1 -0
  158. package/lib/module/utils/useEventListener.js +18 -0
  159. package/lib/module/utils/useEventListener.js.map +1 -0
  160. package/lib/module/videoCompositionPlayer.js +348 -0
  161. package/lib/module/videoCompositionPlayer.js.map +1 -0
  162. package/lib/module/videoFrameImage.js +185 -0
  163. package/lib/module/videoFrameImage.js.map +1 -0
  164. package/lib/module/videoPlayer.js +172 -0
  165. package/lib/module/videoPlayer.js.map +1 -0
  166. package/lib/module/yuvBlit.js +299 -0
  167. package/lib/module/yuvBlit.js.map +1 -0
  168. package/lib/typescript/package.json +1 -0
  169. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts +7 -0
  170. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts.map +1 -0
  171. package/lib/typescript/src/RNSkiaVideoModule.d.ts +4 -0
  172. package/lib/typescript/src/RNSkiaVideoModule.d.ts.map +1 -0
  173. package/lib/typescript/src/canvas.d.ts +4 -0
  174. package/lib/typescript/src/canvas.d.ts.map +1 -0
  175. package/lib/typescript/src/exportCache.d.ts +6 -0
  176. package/lib/typescript/src/exportCache.d.ts.map +1 -0
  177. package/lib/typescript/src/exportFrames.d.ts +57 -0
  178. package/lib/typescript/src/exportFrames.d.ts.map +1 -0
  179. package/lib/typescript/src/exportQueue.d.ts +5 -0
  180. package/lib/typescript/src/exportQueue.d.ts.map +1 -0
  181. package/lib/typescript/src/exportTarget.d.ts +28 -0
  182. package/lib/typescript/src/exportTarget.d.ts.map +1 -0
  183. package/lib/typescript/src/exportVideoComposition.d.ts +68 -0
  184. package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -0
  185. package/lib/typescript/src/frameInterop.d.ts +17 -0
  186. package/lib/typescript/src/frameInterop.d.ts.map +1 -0
  187. package/lib/typescript/src/gpu.d.ts +39 -0
  188. package/lib/typescript/src/gpu.d.ts.map +1 -0
  189. package/lib/typescript/src/gpuDevice.d.ts +11 -0
  190. package/lib/typescript/src/gpuDevice.d.ts.map +1 -0
  191. package/lib/typescript/src/index.d.ts +21 -0
  192. package/lib/typescript/src/index.d.ts.map +1 -0
  193. package/lib/typescript/src/memory.d.ts +25 -0
  194. package/lib/typescript/src/memory.d.ts.map +1 -0
  195. package/lib/typescript/src/previewLifecycle.d.ts +22 -0
  196. package/lib/typescript/src/previewLifecycle.d.ts.map +1 -0
  197. package/lib/typescript/src/runtimeYield.d.ts +4 -0
  198. package/lib/typescript/src/runtimeYield.d.ts.map +1 -0
  199. package/lib/typescript/src/types.d.ts +631 -0
  200. package/lib/typescript/src/types.d.ts.map +1 -0
  201. package/lib/typescript/src/utils/useEventListener.d.ts +5 -0
  202. package/lib/typescript/src/utils/useEventListener.d.ts.map +1 -0
  203. package/lib/typescript/src/videoCompositionPlayer.d.ts +85 -0
  204. package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -0
  205. package/lib/typescript/src/videoFrameImage.d.ts +60 -0
  206. package/lib/typescript/src/videoFrameImage.d.ts.map +1 -0
  207. package/lib/typescript/src/videoPlayer.d.ts +99 -0
  208. package/lib/typescript/src/videoPlayer.d.ts.map +1 -0
  209. package/lib/typescript/src/yuvBlit.d.ts +50 -0
  210. package/lib/typescript/src/yuvBlit.d.ts.map +1 -0
  211. package/package.json +154 -4
  212. package/patches/react-native-skia+3.7.0.patch +502 -0
  213. package/react-native-skia-webgpu-video.podspec +35 -0
  214. package/scripts/apply-skia-canvas-patch.mjs +48 -0
  215. package/scripts/skia-canvas-patch.json +291 -0
  216. package/src/NativeReactNativeSkiaVideo.ts +8 -0
  217. package/src/RNSkiaVideoModule.ts +27 -0
  218. package/src/canvas.ts +22 -0
  219. package/src/exportCache.ts +27 -0
  220. package/src/exportFrames.ts +421 -0
  221. package/src/exportQueue.ts +65 -0
  222. package/src/exportTarget.ts +179 -0
  223. package/src/exportVideoComposition.ts +718 -0
  224. package/src/frameInterop.ts +684 -0
  225. package/src/gpu.ts +290 -0
  226. package/src/gpuDevice.ts +80 -0
  227. package/src/index.tsx +45 -0
  228. package/src/memory.ts +48 -0
  229. package/src/previewLifecycle.ts +60 -0
  230. package/src/runtimeYield.ts +6 -0
  231. package/src/types.ts +661 -0
  232. package/src/utils/useEventListener.ts +22 -0
  233. package/src/videoCompositionPlayer.ts +520 -0
  234. package/src/videoFrameImage.ts +205 -0
  235. package/src/videoPlayer.ts +318 -0
  236. package/src/yuvBlit.ts +331 -0
@@ -0,0 +1,293 @@
1
+ #include "VideoFrame.h"
2
+ #include "RgbaFrameLease.h"
3
+ #include "NativeHardwareBuffer.h"
4
+ #include <limits>
5
+
6
+ namespace RNSkiaVideo {
7
+ namespace {
8
+ // Shared Java backing has one budget reservation, regardless of the number
9
+ // of Java slices or JS ArrayBuffer aliases. This global root keeps its JNI
10
+ // buffer alive; the two-phase Java reaper releases the budget after backing GC.
11
+ class OwnedRgbaBuffer final : public jsi::MutableBuffer, public RgbaFrameStorage {
12
+ public:
13
+ OwnedRgbaBuffer(alias_ref<JByteBuffer> pixels, uint8_t* bytes, size_t size)
14
+ : pixels_(make_global(pixels)), bytes_(bytes), size_(size) {}
15
+ ~OwnedRgbaBuffer() override {
16
+ // Hermes may finalize an external ArrayBuffer on a GC worker.
17
+ ThreadScope attached;
18
+ pixels_.reset();
19
+ }
20
+ size_t size() const override { return size_; }
21
+ uint8_t* data() override { return bytes_; }
22
+ private:
23
+ global_ref<JByteBuffer> pixels_;
24
+ uint8_t* const bytes_;
25
+ const size_t size_;
26
+ };
27
+
28
+ class FramePixelState final {
29
+ public:
30
+ explicit FramePixelState(std::shared_ptr<OwnedRgbaBuffer> pixels)
31
+ : lease_(std::move(pixels)) {}
32
+
33
+ jsi::Value data(jsi::Runtime& runtime) {
34
+ auto storage = lease_.storage();
35
+ if (!storage) return jsi::Value::undefined();
36
+ auto pixels = std::static_pointer_cast<OwnedRgbaBuffer>(storage);
37
+ auto data = jsi::ArrayBuffer(runtime, std::move(pixels));
38
+ data.setExternalMemoryPressure(runtime, storage->size());
39
+ return data;
40
+ }
41
+
42
+ void copyTo(jsi::Runtime& runtime, const jsi::Value* arguments, size_t count) {
43
+ if (count != 1 || !arguments[0].isObject() ||
44
+ !arguments[0].asObject(runtime).isArrayBuffer(runtime))
45
+ throw jsi::JSError(runtime, "RGBA copyPixelsTo requires one ArrayBuffer destination");
46
+ auto destination = arguments[0].asObject(runtime).getArrayBuffer(runtime);
47
+ try {
48
+ lease_.copyTo(destination.data(runtime), destination.size(runtime));
49
+ } catch (const std::exception& error) {
50
+ throw jsi::JSError(runtime, error.what());
51
+ }
52
+ }
53
+
54
+ void dispose() {
55
+ lease_.dispose();
56
+ }
57
+
58
+ private:
59
+ RgbaFrameLease lease_;
60
+ };
61
+
62
+ class HardwareFrameState final {
63
+ public:
64
+ explicit HardwareFrameState(std::shared_ptr<HardwareFrameStorage> owner)
65
+ : owner_(std::move(owner)) {}
66
+ jsi::Value pointer(jsi::Runtime& runtime) {
67
+ std::lock_guard<std::mutex> lock(mutex_);
68
+ if (!owner_) throw jsi::JSError(runtime, "Hardware video frame is disposed");
69
+ return jsi::BigInt::fromUint64(runtime, reinterpret_cast<uintptr_t>(owner_->buffer));
70
+ }
71
+ void dispose() {
72
+ std::lock_guard<std::mutex> lock(mutex_);
73
+ owner_.reset();
74
+ }
75
+ private:
76
+ std::mutex mutex_;
77
+ std::shared_ptr<HardwareFrameStorage> owner_;
78
+ };
79
+ }
80
+
81
+ jint VideoFrame::getWidth() {
82
+ static const auto method = getClass()->getMethod<jint()>("getWidth");
83
+ return method(self());
84
+ }
85
+ jint VideoFrame::getHeight() {
86
+ static const auto method = getClass()->getMethod<jint()>("getHeight");
87
+ return method(self());
88
+ }
89
+ jint VideoFrame::getRotation() {
90
+ static const auto method = getClass()->getMethod<jint()>("getRotation");
91
+ return method(self());
92
+ }
93
+ jint VideoFrame::getBytesPerRow() {
94
+ static const auto method = getClass()->getMethod<jint()>("getBytesPerRow");
95
+ return method(self());
96
+ }
97
+ jlong VideoFrame::getTimestampNs() {
98
+ static const auto method = getClass()->getMethod<jlong()>("getTimestampNs");
99
+ return method(self());
100
+ }
101
+ jlong VideoFrame::getId() {
102
+ static const auto method = getClass()->getMethod<jlong()>("getId");
103
+ return method(self());
104
+ }
105
+ jlong VideoFrame::getProducerId() {
106
+ static const auto method = getClass()->getMethod<jlong()>("getProducerId");
107
+ return method(self());
108
+ }
109
+ local_ref<JByteBuffer> VideoFrame::getPixelBuffer() {
110
+ static const auto method = getClass()->getMethod<JByteBuffer()>("getPixelBuffer");
111
+ return method(self());
112
+ }
113
+ jboolean VideoFrame::isHardwareBuffer() {
114
+ static const auto method = getClass()->getMethod<jboolean()>("isHardwareBuffer");
115
+ return method(self());
116
+ }
117
+ jlong VideoFrame::getNativeHardwareBufferHandle() {
118
+ static const auto method = getClass()->getMethod<jlong()>("getNativeHardwareBufferHandle");
119
+ return method(self());
120
+ }
121
+ void VideoFrame::close() {
122
+ static const auto method = getClass()->getMethod<void()>("close");
123
+ method(self());
124
+ }
125
+
126
+ jsi::Value VideoFrame::toJS(jsi::Runtime& runtime) {
127
+ try {
128
+ auto result = toJSInternal(runtime);
129
+ close();
130
+ return result;
131
+ } catch (...) { close(); throw; }
132
+ }
133
+
134
+ jsi::Value VideoFrame::toJSInternal(jsi::Runtime& runtime) {
135
+ const auto width = getWidth();
136
+ const auto height = getHeight();
137
+ const auto stride = getBytesPerRow();
138
+ if (isHardwareBuffer()) {
139
+ auto owner = acquireHardwareFrame(getNativeHardwareBufferHandle());
140
+ // An ImageReader frame is the codec's YUV buffer: its picture is the crop
141
+ // rectangle, turned for display when WebGPU samples it.
142
+ const bool decodedImage = owner->image != nullptr;
143
+ const bool swapped = owner->rotation == 90 || owner->rotation == 270;
144
+ const bool valid = decodedImage
145
+ ? owner->description.format != AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM &&
146
+ (swapped ? owner->cropHeight : owner->cropWidth) == width &&
147
+ (swapped ? owner->cropWidth : owner->cropHeight) == height
148
+ : owner->description.format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM &&
149
+ owner->description.width == static_cast<uint32_t>(width) &&
150
+ owner->description.height == static_cast<uint32_t>(height);
151
+ if (width <= 0 || height <= 0 || owner->description.layers != 1 || !valid)
152
+ throw jsi::JSError(runtime, "Invalid owned Android hardware frame");
153
+ const uint64_t bytes = decodedImage
154
+ ? static_cast<uint64_t>(owner->description.stride) * owner->description.height * 3 / 2
155
+ : static_cast<uint64_t>(owner->description.stride) * height * 4;
156
+ jsi::Value region = jsi::Value::undefined();
157
+ if (decodedImage) {
158
+ auto rectangle = jsi::Object(runtime);
159
+ rectangle.setProperty(runtime, "x", owner->cropX);
160
+ rectangle.setProperty(runtime, "y", owner->cropY);
161
+ rectangle.setProperty(runtime, "width", owner->cropWidth);
162
+ rectangle.setProperty(runtime, "height", owner->cropHeight);
163
+ region = jsi::Value(runtime, rectangle);
164
+ }
165
+ const int sourceRotation = owner->rotation;
166
+ auto state = std::make_shared<HardwareFrameState>(std::move(owner));
167
+ auto frame = jsi::Object(runtime);
168
+ frame.setProperty(runtime, "id", static_cast<double>(getId()));
169
+ frame.setProperty(runtime, "producerId", static_cast<double>(getProducerId()));
170
+ frame.setProperty(runtime, "width", width); frame.setProperty(runtime, "height", height);
171
+ frame.setProperty(runtime, "rotation", getRotation());
172
+ frame.setProperty(runtime, "timestamp", static_cast<double>(getTimestampNs()) / 1e9);
173
+ auto holder = jsi::Object(runtime);
174
+ holder.setExternalMemoryPressure(runtime, bytes);
175
+ auto texture = jsi::Object(runtime);
176
+ texture.setProperty(runtime, "kind", jsi::String::createFromAscii(runtime, "native-buffer"));
177
+ if (decodedImage) {
178
+ texture.setProperty(runtime, "region", region);
179
+ texture.setProperty(runtime, "rotation", sourceRotation);
180
+ }
181
+ auto getter = jsi::Function::createFromHostFunction(runtime,
182
+ jsi::PropNameID::forAscii(runtime, "nativeBuffer"), 0,
183
+ [state](jsi::Runtime& rt, const jsi::Value&, const jsi::Value*, size_t) -> jsi::Value {
184
+ return state->pointer(rt);
185
+ });
186
+ auto descriptor = jsi::Object(runtime);
187
+ descriptor.setProperty(runtime, "enumerable", true);
188
+ descriptor.setProperty(runtime, "get", getter.getPropertyAsFunction(runtime, "bind")
189
+ .callWithThis(runtime, getter, holder));
190
+ runtime.global().getPropertyAsObject(runtime, "Object")
191
+ .getPropertyAsFunction(runtime, "defineProperty")
192
+ .call(runtime, texture, jsi::String::createFromAscii(runtime, "nativeBuffer"), descriptor);
193
+ frame.setProperty(runtime, "texture", std::move(texture));
194
+ auto dispose = jsi::Function::createFromHostFunction(runtime,
195
+ jsi::PropNameID::forAscii(runtime, "dispose"), 0,
196
+ [state](jsi::Runtime& rt, const jsi::Value& receiver, const jsi::Value*, size_t) -> jsi::Value {
197
+ try { receiver.asObject(rt).setExternalMemoryPressure(rt, 0); }
198
+ catch (...) { state->dispose(); throw; }
199
+ state->dispose();
200
+ return jsi::Value::undefined();
201
+ });
202
+ frame.setProperty(runtime, "dispose", dispose.getPropertyAsFunction(runtime, "bind")
203
+ .callWithThis(runtime, dispose, holder));
204
+ return frame;
205
+ }
206
+ auto pixels = getPixelBuffer();
207
+ auto* env = Environment::current();
208
+ const auto capacity = env->GetDirectBufferCapacity(pixels.get());
209
+ auto* bytes = static_cast<uint8_t*>(env->GetDirectBufferAddress(pixels.get()));
210
+ const size_t size = static_cast<size_t>(stride) * static_cast<size_t>(height);
211
+ if (width <= 0 || height <= 0 || stride != static_cast<int64_t>(width) * 4 ||
212
+ capacity < 0 || size != static_cast<size_t>(capacity) || !bytes) {
213
+ throw jsi::JSError(runtime, "Invalid owned Android RGBA frame");
214
+ }
215
+ auto state = std::make_shared<FramePixelState>(
216
+ std::make_shared<OwnedRgbaBuffer>(pixels, bytes, size));
217
+
218
+ auto frame = jsi::Object(runtime);
219
+ frame.setProperty(runtime, "id", static_cast<double>(getId()));
220
+ frame.setProperty(runtime, "producerId", static_cast<double>(getProducerId()));
221
+ frame.setProperty(runtime, "width", width);
222
+ frame.setProperty(runtime, "height", height);
223
+ frame.setProperty(runtime, "rotation", getRotation());
224
+ frame.setProperty(runtime, "timestamp", static_cast<double>(getTimestampNs()) / 1e9);
225
+ auto texture = jsi::Object(runtime);
226
+ texture.setProperty(runtime, "kind", jsi::String::createFromAscii(runtime, "rgba"));
227
+ texture.setProperty(runtime, "width", width);
228
+ texture.setProperty(runtime, "height", height);
229
+ texture.setProperty(runtime, "bytesPerRow", stride);
230
+ auto copyPixelsTo = jsi::Function::createFromHostFunction(runtime,
231
+ jsi::PropNameID::forAscii(runtime, "copyPixelsTo"), 1,
232
+ [state](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value {
233
+ state->copyTo(rt, args, count);
234
+ return jsi::Value::undefined();
235
+ });
236
+ auto descriptor = jsi::Object(runtime);
237
+ descriptor.setProperty(runtime, "enumerable", true);
238
+ // Cache only in a GC-traced JS object. A C++ strong ArrayBuffer handle would
239
+ // leak a cycle if a consumer attached its frame/texture to that ArrayBuffer.
240
+ auto cache = jsi::Object(runtime);
241
+ cache.setProperty(runtime, "data", jsi::Value::undefined());
242
+ // The normal scoped-copy path still owns native Java storage until dispose.
243
+ // Credit that real ownership even if no external ArrayBuffer is materialized.
244
+ cache.setExternalMemoryPressure(runtime, size);
245
+ texture.setProperty(runtime, "copyPixelsTo",
246
+ copyPixelsTo.getPropertyAsFunction(runtime, "bind")
247
+ .callWithThis(runtime, copyPixelsTo, cache));
248
+ auto getter = jsi::Function::createFromHostFunction(runtime,
249
+ jsi::PropNameID::forAscii(runtime, "data"), 0,
250
+ [state](jsi::Runtime& rt, const jsi::Value& receiver, const jsi::Value*, size_t) -> jsi::Value {
251
+ auto holder = receiver.asObject(rt);
252
+ auto cached = holder.getProperty(rt, "data");
253
+ if (!cached.isUndefined()) return cached;
254
+ auto data = state->data(rt);
255
+ holder.setProperty(rt, "data", data);
256
+ // Materialized data has its own pressure credit and can outlive the frame.
257
+ // Transfer instead of persistently crediting two aliases of one backing.
258
+ holder.setExternalMemoryPressure(rt, 0);
259
+ return data;
260
+ });
261
+ // Bound receivers are JS edges traced by Hermes. Extracted accessors and
262
+ // dispose functions retain the correct holder without C++ -> JS roots.
263
+ auto boundGetter = getter.getPropertyAsFunction(runtime, "bind")
264
+ .callWithThis(runtime, getter, cache);
265
+ descriptor.setProperty(runtime, "get", std::move(boundGetter));
266
+ // A getter materializes the legacy external buffer only when explicitly read.
267
+ // Normal rendering copies into one reusable JS scratch and creates no per-frame
268
+ // external ArrayBuffer root coupling Java's heap to Hermes' GC schedule.
269
+ runtime.global().getPropertyAsObject(runtime, "Object")
270
+ .getPropertyAsFunction(runtime, "defineProperty")
271
+ .call(runtime, texture, jsi::String::createFromAscii(runtime, "data"), descriptor);
272
+ frame.setProperty(runtime, "texture", std::move(texture));
273
+ // Release the frame's JNI root promptly, including an extracted dispose call.
274
+ // Explicit data aliases hold their own shared storage and remain valid.
275
+ auto dispose = jsi::Function::createFromHostFunction(runtime,
276
+ jsi::PropNameID::forAscii(runtime, "dispose"), 0,
277
+ [state](jsi::Runtime& rt, const jsi::Value& receiver, const jsi::Value*, size_t) -> jsi::Value {
278
+ try {
279
+ auto holder = receiver.asObject(rt);
280
+ holder.setProperty(rt, "data", jsi::Value::undefined());
281
+ holder.setExternalMemoryPressure(rt, 0);
282
+ } catch (...) {
283
+ state->dispose();
284
+ throw;
285
+ }
286
+ state->dispose();
287
+ return jsi::Value::undefined();
288
+ });
289
+ frame.setProperty(runtime, "dispose", dispose.getPropertyAsFunction(runtime, "bind")
290
+ .callWithThis(runtime, dispose, cache));
291
+ return frame;
292
+ }
293
+ } // namespace RNSkiaVideo
@@ -0,0 +1,27 @@
1
+ #pragma once
2
+
3
+ #include <fbjni/fbjni.h>
4
+ #include <fbjni/ByteBuffer.h>
5
+ #include <jsi/jsi.h>
6
+
7
+ namespace RNSkiaVideo {
8
+ using namespace facebook;
9
+ using namespace jni;
10
+
11
+ struct VideoFrame : JavaClass<VideoFrame> {
12
+ static constexpr auto kJavaDescriptor = "Lcom/azzapp/rnskv/VideoFrame;";
13
+ jint getWidth();
14
+ jint getHeight();
15
+ jint getRotation();
16
+ jint getBytesPerRow();
17
+ jlong getTimestampNs();
18
+ jlong getId();
19
+ jlong getProducerId();
20
+ local_ref<JByteBuffer> getPixelBuffer();
21
+ jboolean isHardwareBuffer();
22
+ jlong getNativeHardwareBufferHandle();
23
+ void close();
24
+ jsi::Value toJS(jsi::Runtime& runtime);
25
+ jsi::Value toJSInternal(jsi::Runtime& runtime);
26
+ };
27
+ } // namespace RNSkiaVideo
@@ -0,0 +1,96 @@
1
+ #include "VideoPlayer.h"
2
+
3
+ namespace RNSkiaVideo {
4
+
5
+ jni::local_ref<VideoPlayer>
6
+ VideoPlayer::create(const std::string& uri, int width, int height,
7
+ alias_ref<NativeEventDispatcher> dispatcher) {
8
+ return newInstance(uri, width, height, dispatcher);
9
+ }
10
+
11
+ void VideoPlayer::play() {
12
+ static const auto playMethod = getClass()->getMethod<void()>("play");
13
+ playMethod(self());
14
+ }
15
+
16
+ void VideoPlayer::setupGL() {
17
+ static const auto setupGLMethod = getClass()->getMethod<void()>("setupGL");
18
+ setupGLMethod(self());
19
+ }
20
+
21
+ void VideoPlayer::pause() {
22
+ static const auto pauseMethod = getClass()->getMethod<void()>("pause");
23
+ pauseMethod(self());
24
+ }
25
+
26
+ void VideoPlayer::seekTo(jlong location) {
27
+ static const auto seekToMethod = getClass()->getMethod<void(jlong)>("seekTo");
28
+ seekToMethod(self(), location);
29
+ }
30
+
31
+ jboolean VideoPlayer::getIsPlaying() {
32
+ static const auto getIsPlayingMethod =
33
+ getClass()->getMethod<jboolean()>("getIsPlaying");
34
+ return getIsPlayingMethod(self());
35
+ }
36
+
37
+ jlong VideoPlayer::getCurrentPosition() {
38
+ static const auto getCurrentPositionMethod =
39
+ getClass()->getMethod<jlong()>("getCurrentPosition");
40
+ return getCurrentPositionMethod(self());
41
+ }
42
+
43
+ jlong VideoPlayer::getDuration() {
44
+ static const auto getDurationMethod =
45
+ getClass()->getMethod<jlong()>("getDuration");
46
+ return getDurationMethod(self());
47
+ }
48
+
49
+ jfloat VideoPlayer::getVolume() {
50
+ static const auto getVolumeMethod =
51
+ getClass()->getMethod<jfloat()>("getVolume");
52
+ return getVolumeMethod(self());
53
+ }
54
+
55
+ void VideoPlayer::setVolume(jfloat volume) {
56
+ static const auto setVolumeMethod =
57
+ getClass()->getMethod<void(jfloat)>("setVolume");
58
+ setVolumeMethod(self(), volume);
59
+ }
60
+
61
+ jboolean VideoPlayer::getIsLooping() {
62
+ static const auto getIsLoopingMethod =
63
+ getClass()->getMethod<jboolean()>("getIsLooping");
64
+ return getIsLoopingMethod(self());
65
+ }
66
+
67
+ void VideoPlayer::setIsLooping(jboolean isLooping) {
68
+ static const auto setIsLoopingMethod =
69
+ getClass()->getMethod<void(bool)>("setIsLooping");
70
+ setIsLoopingMethod(self(), isLooping);
71
+ }
72
+
73
+ jfloat VideoPlayer::getPlaybackSpeed() {
74
+ static const auto getPlaybackSpeedMethod =
75
+ getClass()->getMethod<jfloat()>("getPlaybackSpeed");
76
+ return getPlaybackSpeedMethod(self());
77
+ }
78
+
79
+ void VideoPlayer::setPlaybackSpeed(jfloat playbackSpeed) {
80
+ static const auto setPlaybackSpeedMethod =
81
+ getClass()->getMethod<void(jfloat)>("setPlaybackSpeed");
82
+ setPlaybackSpeedMethod(self(), playbackSpeed);
83
+ }
84
+
85
+ local_ref<VideoFrame> VideoPlayer::decodeNextFrame() {
86
+ static const auto decodeNextFrameMethod =
87
+ getClass()->getMethod<VideoFrame()>("decodeNextFrame");
88
+ return decodeNextFrameMethod(self());
89
+ }
90
+
91
+ void VideoPlayer::release() {
92
+ static const auto releaseMethod = getClass()->getMethod<void()>("release");
93
+ releaseMethod(self());
94
+ }
95
+
96
+ } // namespace RNSkiaVideo
@@ -0,0 +1,52 @@
1
+ #pragma once
2
+
3
+ #include <android/hardware_buffer_jni.h>
4
+ #include <fbjni/fbjni.h>
5
+
6
+ #include "NativeEventDispatcher.h"
7
+ #include "VideoFrame.h"
8
+
9
+ namespace RNSkiaVideo {
10
+
11
+ using namespace facebook;
12
+
13
+ struct VideoPlayer : public jni::JavaClass<VideoPlayer> {
14
+
15
+ public:
16
+ static constexpr auto kJavaDescriptor = "Lcom/azzapp/rnskv/VideoPlayer;";
17
+
18
+ static jni::local_ref<VideoPlayer>
19
+ create(const std::string& uri, int width, int height,
20
+ alias_ref<RNSkiaVideo::NativeEventDispatcher> dispatcher);
21
+
22
+ void play();
23
+
24
+ void pause();
25
+
26
+ void setupGL();
27
+
28
+ void seekTo(jlong location);
29
+
30
+ jboolean getIsPlaying();
31
+
32
+ jlong getCurrentPosition();
33
+
34
+ jlong getDuration();
35
+
36
+ jfloat getVolume();
37
+
38
+ void setVolume(jfloat volume);
39
+
40
+ jboolean getIsLooping();
41
+
42
+ void setIsLooping(jboolean isLooping);
43
+
44
+ jfloat getPlaybackSpeed();
45
+
46
+ void setPlaybackSpeed(jfloat playbackSpeed);
47
+
48
+ local_ref<VideoFrame> decodeNextFrame();
49
+
50
+ void release();
51
+ };
52
+ } // namespace RNSkiaVideo