react-native-skia-webgpu-video 0.0.0-stage → 0.1.0-beta.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 (237) 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/DisplayVsyncPeriod.java +72 -0
  48. package/android/src/main/java/com/azzapp/rnskv/EGLResourcesHolder.java +132 -0
  49. package/android/src/main/java/com/azzapp/rnskv/EGLUtils.java +225 -0
  50. package/android/src/main/java/com/azzapp/rnskv/FrameSize.java +50 -0
  51. package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +510 -0
  52. package/android/src/main/java/com/azzapp/rnskv/JNIHelpers.java +12 -0
  53. package/android/src/main/java/com/azzapp/rnskv/NativeEventDispatcher.java +25 -0
  54. package/android/src/main/java/com/azzapp/rnskv/NativeHardwareBuffer.java +62 -0
  55. package/android/src/main/java/com/azzapp/rnskv/NativeRgbaBuffer.java +43 -0
  56. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoModule.java +45 -0
  57. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoPackage.java +43 -0
  58. package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoSpec.java +9 -0
  59. package/android/src/main/java/com/azzapp/rnskv/RgbaBufferAllocator.java +114 -0
  60. package/android/src/main/java/com/azzapp/rnskv/RgbaLayout.java +33 -0
  61. package/android/src/main/java/com/azzapp/rnskv/SurfaceOutputPolicy.java +13 -0
  62. package/android/src/main/java/com/azzapp/rnskv/TextureRenderer.java +150 -0
  63. package/android/src/main/java/com/azzapp/rnskv/TimeHelpers.java +11 -0
  64. package/android/src/main/java/com/azzapp/rnskv/VideoCapabilities.java +354 -0
  65. package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +147 -0
  66. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +779 -0
  67. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +463 -0
  68. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractorSync.java +241 -0
  69. package/android/src/main/java/com/azzapp/rnskv/VideoCompositionItemDecoder.java +492 -0
  70. package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +639 -0
  71. package/android/src/main/java/com/azzapp/rnskv/VideoFrame.java +74 -0
  72. package/android/src/main/java/com/azzapp/rnskv/VideoPlayer.java +361 -0
  73. package/cpp/RNSVCheckedSizes.h +54 -0
  74. package/cpp/RNSVEventEmitter.cpp +199 -0
  75. package/cpp/RNSVEventEmitter.h +31 -0
  76. package/cpp/RNSVHostObject.cpp +79 -0
  77. package/cpp/RNSVHostObject.h +67 -0
  78. package/cpp/RNSVMemoryBudget.cpp +68 -0
  79. package/cpp/RNSVMemoryBudget.h +100 -0
  80. package/cpp/RNSVRuntimeLifecycleMonitor.cpp +88 -0
  81. package/cpp/RNSVRuntimeLifecycleMonitor.h +33 -0
  82. package/docs/ANDROID_DEVICE_306.md +95 -0
  83. package/docs/ANDROID_EXPORT_CACHE_306.md +48 -0
  84. package/docs/ANDROID_MEMORY_RETENTION_306.md +67 -0
  85. package/docs/BENCHMARKS.md +368 -0
  86. package/docs/IOS_DEVICE_TESTS.md +115 -0
  87. package/docs/IOS_SIMULATOR_TESTS.md +74 -0
  88. package/docs/MIGRATION.md +82 -0
  89. package/docs/NATIVE_MEMORY_INVESTIGATION_306.md +88 -0
  90. package/docs/PERFORMANCE_AB_IPHONE_303.md +196 -0
  91. package/docs/PERFORMANCE_AB_SIMULATOR.md +135 -0
  92. package/docs/PERFORMANCE_AB_SOAK_SIMULATOR.md +84 -0
  93. package/docs/PERFORMANCE_IPHONE_306_OPTIMIZATION.md +76 -0
  94. package/docs/PERFORMANCE_SIMULATOR.md +89 -0
  95. package/docs/PERFORMANCE_SIMULATOR_INITIAL.md +96 -0
  96. package/docs/PERFORMANCE_SOAK_IPHONE_303.md +41 -0
  97. package/docs/UPSTREAM_NOTES.md +188 -0
  98. package/docs/assets/banner.svg +26 -0
  99. package/examples/webgpu-overlay.ts +147 -0
  100. package/ios/AVAssetTrackUtils.h +24 -0
  101. package/ios/AudioCompositionUtils.h +27 -0
  102. package/ios/AudioCompositionUtils.mm +113 -0
  103. package/ios/DecoderWindow.h +52 -0
  104. package/ios/NativeResourceStats.h +32 -0
  105. package/ios/RNSVColorSpace.h +50 -0
  106. package/ios/RNSVJSIUtils.h +46 -0
  107. package/ios/RNSVScopedPixelBuffer.h +17 -0
  108. package/ios/RNSVVideoPlayer.h +46 -0
  109. package/ios/RNSVVideoPlayer.mm +410 -0
  110. package/ios/ReactNativeSkiaVideo.h +8 -0
  111. package/ios/ReactNativeSkiaVideo.mm +310 -0
  112. package/ios/SeekPolicy.h +27 -0
  113. package/ios/VideoComposition.h +148 -0
  114. package/ios/VideoCompositionFramesExtractorHostObject.h +105 -0
  115. package/ios/VideoCompositionFramesExtractorHostObject.mm +686 -0
  116. package/ios/VideoCompositionFramesExtractorSyncHostObject.h +37 -0
  117. package/ios/VideoCompositionFramesExtractorSyncHostObject.mm +168 -0
  118. package/ios/VideoCompositionItemDecoder.h +60 -0
  119. package/ios/VideoCompositionItemDecoder.mm +480 -0
  120. package/ios/VideoEncoderHostObject.h +83 -0
  121. package/ios/VideoEncoderHostObject.mm +706 -0
  122. package/ios/VideoFrame.h +66 -0
  123. package/ios/VideoFrame.mm +108 -0
  124. package/ios/VideoPlayerHostObject.h +52 -0
  125. package/ios/VideoPlayerHostObject.mm +332 -0
  126. package/lib/module/NativeReactNativeSkiaVideo.js +5 -0
  127. package/lib/module/NativeReactNativeSkiaVideo.js.map +1 -0
  128. package/lib/module/RNSkiaVideoModule.js +20 -0
  129. package/lib/module/RNSkiaVideoModule.js.map +1 -0
  130. package/lib/module/canvas.js +18 -0
  131. package/lib/module/canvas.js.map +1 -0
  132. package/lib/module/exportCache.js +22 -0
  133. package/lib/module/exportCache.js.map +1 -0
  134. package/lib/module/exportFrames.js +338 -0
  135. package/lib/module/exportFrames.js.map +1 -0
  136. package/lib/module/exportQueue.js +52 -0
  137. package/lib/module/exportQueue.js.map +1 -0
  138. package/lib/module/exportTarget.js +137 -0
  139. package/lib/module/exportTarget.js.map +1 -0
  140. package/lib/module/exportVideoComposition.js +492 -0
  141. package/lib/module/exportVideoComposition.js.map +1 -0
  142. package/lib/module/frameInterop.js +532 -0
  143. package/lib/module/frameInterop.js.map +1 -0
  144. package/lib/module/gpu.js +222 -0
  145. package/lib/module/gpu.js.map +1 -0
  146. package/lib/module/gpuDevice.js +54 -0
  147. package/lib/module/gpuDevice.js.map +1 -0
  148. package/lib/module/index.js +36 -0
  149. package/lib/module/index.js.map +1 -0
  150. package/lib/module/memory.js +38 -0
  151. package/lib/module/memory.js.map +1 -0
  152. package/lib/module/package.json +1 -0
  153. package/lib/module/previewLifecycle.js +52 -0
  154. package/lib/module/previewLifecycle.js.map +1 -0
  155. package/lib/module/runtimeYield.js +10 -0
  156. package/lib/module/runtimeYield.js.map +1 -0
  157. package/lib/module/types.js +4 -0
  158. package/lib/module/types.js.map +1 -0
  159. package/lib/module/utils/useEventListener.js +18 -0
  160. package/lib/module/utils/useEventListener.js.map +1 -0
  161. package/lib/module/videoCompositionPlayer.js +368 -0
  162. package/lib/module/videoCompositionPlayer.js.map +1 -0
  163. package/lib/module/videoFrameImage.js +185 -0
  164. package/lib/module/videoFrameImage.js.map +1 -0
  165. package/lib/module/videoPlayer.js +172 -0
  166. package/lib/module/videoPlayer.js.map +1 -0
  167. package/lib/module/yuvBlit.js +299 -0
  168. package/lib/module/yuvBlit.js.map +1 -0
  169. package/lib/typescript/package.json +1 -0
  170. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts +7 -0
  171. package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts.map +1 -0
  172. package/lib/typescript/src/RNSkiaVideoModule.d.ts +4 -0
  173. package/lib/typescript/src/RNSkiaVideoModule.d.ts.map +1 -0
  174. package/lib/typescript/src/canvas.d.ts +4 -0
  175. package/lib/typescript/src/canvas.d.ts.map +1 -0
  176. package/lib/typescript/src/exportCache.d.ts +6 -0
  177. package/lib/typescript/src/exportCache.d.ts.map +1 -0
  178. package/lib/typescript/src/exportFrames.d.ts +57 -0
  179. package/lib/typescript/src/exportFrames.d.ts.map +1 -0
  180. package/lib/typescript/src/exportQueue.d.ts +5 -0
  181. package/lib/typescript/src/exportQueue.d.ts.map +1 -0
  182. package/lib/typescript/src/exportTarget.d.ts +28 -0
  183. package/lib/typescript/src/exportTarget.d.ts.map +1 -0
  184. package/lib/typescript/src/exportVideoComposition.d.ts +68 -0
  185. package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -0
  186. package/lib/typescript/src/frameInterop.d.ts +17 -0
  187. package/lib/typescript/src/frameInterop.d.ts.map +1 -0
  188. package/lib/typescript/src/gpu.d.ts +39 -0
  189. package/lib/typescript/src/gpu.d.ts.map +1 -0
  190. package/lib/typescript/src/gpuDevice.d.ts +11 -0
  191. package/lib/typescript/src/gpuDevice.d.ts.map +1 -0
  192. package/lib/typescript/src/index.d.ts +21 -0
  193. package/lib/typescript/src/index.d.ts.map +1 -0
  194. package/lib/typescript/src/memory.d.ts +25 -0
  195. package/lib/typescript/src/memory.d.ts.map +1 -0
  196. package/lib/typescript/src/previewLifecycle.d.ts +22 -0
  197. package/lib/typescript/src/previewLifecycle.d.ts.map +1 -0
  198. package/lib/typescript/src/runtimeYield.d.ts +4 -0
  199. package/lib/typescript/src/runtimeYield.d.ts.map +1 -0
  200. package/lib/typescript/src/types.d.ts +631 -0
  201. package/lib/typescript/src/types.d.ts.map +1 -0
  202. package/lib/typescript/src/utils/useEventListener.d.ts +5 -0
  203. package/lib/typescript/src/utils/useEventListener.d.ts.map +1 -0
  204. package/lib/typescript/src/videoCompositionPlayer.d.ts +105 -0
  205. package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -0
  206. package/lib/typescript/src/videoFrameImage.d.ts +60 -0
  207. package/lib/typescript/src/videoFrameImage.d.ts.map +1 -0
  208. package/lib/typescript/src/videoPlayer.d.ts +99 -0
  209. package/lib/typescript/src/videoPlayer.d.ts.map +1 -0
  210. package/lib/typescript/src/yuvBlit.d.ts +50 -0
  211. package/lib/typescript/src/yuvBlit.d.ts.map +1 -0
  212. package/package.json +155 -4
  213. package/patches/react-native-skia+3.7.0.patch +502 -0
  214. package/react-native-skia-webgpu-video.podspec +35 -0
  215. package/scripts/apply-skia-canvas-patch.mjs +48 -0
  216. package/scripts/skia-canvas-patch.json +291 -0
  217. package/src/NativeReactNativeSkiaVideo.ts +8 -0
  218. package/src/RNSkiaVideoModule.ts +27 -0
  219. package/src/canvas.ts +22 -0
  220. package/src/exportCache.ts +27 -0
  221. package/src/exportFrames.ts +421 -0
  222. package/src/exportQueue.ts +65 -0
  223. package/src/exportTarget.ts +179 -0
  224. package/src/exportVideoComposition.ts +718 -0
  225. package/src/frameInterop.ts +684 -0
  226. package/src/gpu.ts +290 -0
  227. package/src/gpuDevice.ts +80 -0
  228. package/src/index.tsx +45 -0
  229. package/src/memory.ts +48 -0
  230. package/src/previewLifecycle.ts +60 -0
  231. package/src/runtimeYield.ts +6 -0
  232. package/src/types.ts +661 -0
  233. package/src/utils/useEventListener.ts +22 -0
  234. package/src/videoCompositionPlayer.ts +568 -0
  235. package/src/videoFrameImage.ts +205 -0
  236. package/src/videoPlayer.ts +318 -0
  237. package/src/yuvBlit.ts +331 -0
@@ -0,0 +1,133 @@
1
+ #include "VideoCompositionFramesExtractorSyncHostObject.h"
2
+ #include "ReturnedVideoFrames.h"
3
+ #include <cmath>
4
+
5
+ namespace RNSkiaVideo {
6
+ using namespace facebook::jni;
7
+
8
+ local_ref<VideoCompositionFramesExtractorSync>
9
+ VideoCompositionFramesExtractorSync::create(
10
+ alias_ref<VideoComposition> composition) {
11
+ return newInstance(composition);
12
+ }
13
+
14
+ void VideoCompositionFramesExtractorSync::start() const {
15
+ static const auto startMethod = getClass()->getMethod<void()>("start");
16
+ startMethod(self());
17
+ }
18
+
19
+ local_ref<JMap<JString, VideoFrame>>
20
+ VideoCompositionFramesExtractorSync::decodeCompositionFrames(jdouble time) {
21
+ static const auto decodeCompositionFramesMethod =
22
+ getClass()->getMethod<JMap<JString, VideoFrame>(jdouble)>(
23
+ "decodeCompositionFrames");
24
+ return decodeCompositionFramesMethod(self(), time);
25
+ }
26
+
27
+ void VideoCompositionFramesExtractorSync::release() const {
28
+ static const auto releaseMethod = getClass()->getMethod<void()>("release");
29
+ releaseMethod(self());
30
+ }
31
+
32
+ VideoCompositionFramesExtractorSyncHostObject::
33
+ VideoCompositionFramesExtractorSyncHostObject(jsi::Runtime& runtime,
34
+ jsi::Object jsComposition) {
35
+ auto composition = VideoComposition::fromJSIObject(runtime, jsComposition);
36
+ framesExtractor =
37
+ make_global(VideoCompositionFramesExtractorSync::create(composition));
38
+ }
39
+
40
+ VideoCompositionFramesExtractorSyncHostObject::
41
+ ~VideoCompositionFramesExtractorSyncHostObject() {
42
+ this->release();
43
+ }
44
+
45
+ std::vector<jsi::PropNameID>
46
+ VideoCompositionFramesExtractorSyncHostObject::getPropertyNames(
47
+ jsi::Runtime& rt) {
48
+ std::vector<jsi::PropNameID> result;
49
+ result.push_back(jsi::PropNameID::forUtf8(rt, std::string("start")));
50
+ result.push_back(
51
+ jsi::PropNameID::forUtf8(rt, std::string("decodeCompositionFrames")));
52
+ result.push_back(jsi::PropNameID::forUtf8(rt, std::string("dispose")));
53
+ return result;
54
+ }
55
+
56
+ // The methods are created once per runtime (see RNSVHostObject):
57
+ // `decodeCompositionFrames` is read for every exported frame.
58
+ jsi::Value VideoCompositionFramesExtractorSyncHostObject::get(
59
+ jsi::Runtime& runtime, const jsi::PropNameID& propNameId) {
60
+ auto propName = propNameId.utf8(runtime);
61
+ if (propName == "decodeCompositionFrames") {
62
+ return getFunction(
63
+ runtime, propName, 1,
64
+ [this](jsi::Runtime& runtime, const jsi::Value& thisValue,
65
+ const jsi::Value* arguments, size_t count) -> jsi::Value {
66
+ auto result = jsi::Object(runtime);
67
+ global_ref<VideoCompositionFramesExtractorSync> current;
68
+ {
69
+ std::lock_guard<std::mutex> lock(extractorMutex);
70
+ if (released.test()) return result;
71
+ current = make_global(framesExtractor);
72
+ }
73
+ if (count < 1 || !arguments[0].isNumber() || !std::isfinite(arguments[0].asNumber()))
74
+ throw jsi::JSError(runtime, "Expected a finite video decode time");
75
+ auto time = arguments[0].asNumber();
76
+ // Do not hold the C++ mutex while Java waits: dispose must be able
77
+ // to cancel that request on another runtime.
78
+ auto frames = current->decodeCompositionFrames(time);
79
+ ReturnedVideoFrames returned(frames);
80
+ for (auto& entry : *frames) {
81
+ auto id = entry.first->toStdString();
82
+ auto frame = entry.second;
83
+ if (frame->isHardwareBuffer()) {
84
+ result.setProperty(runtime, id.c_str(), frame->toJS(runtime));
85
+ continue;
86
+ }
87
+ auto cached = getVersionedObject(runtime, "frame:" + id,
88
+ static_cast<double>(frame->getId()), [&](jsi::Object& holder) {
89
+ holder.setProperty(runtime, "value", frame->toJS(runtime));
90
+ });
91
+ result.setProperty(runtime, id.c_str(), cached.asObject(runtime).getProperty(runtime, "value"));
92
+ }
93
+ returned.close();
94
+ return result;
95
+ });
96
+ } else if (propName == "start") {
97
+ return getFunction(
98
+ runtime, propName, 0,
99
+ [this](jsi::Runtime& runtime, const jsi::Value& thisValue,
100
+ const jsi::Value* arguments, size_t count) -> jsi::Value {
101
+ global_ref<VideoCompositionFramesExtractorSync> current;
102
+ {
103
+ std::lock_guard<std::mutex> lock(extractorMutex);
104
+ if (released.test()) return jsi::Value::undefined();
105
+ current = make_global(framesExtractor);
106
+ }
107
+ current->start();
108
+ return jsi::Value::undefined();
109
+ });
110
+ }
111
+ if (propName == "dispose") {
112
+ return getFunction(
113
+ runtime, propName, 0,
114
+ [this](jsi::Runtime& runtime, const jsi::Value& thisValue,
115
+ const jsi::Value* arguments, size_t count) -> jsi::Value {
116
+ this->release();
117
+ return jsi::Value::undefined();
118
+ });
119
+ }
120
+ return jsi::Value::undefined();
121
+ }
122
+
123
+ void VideoCompositionFramesExtractorSyncHostObject::release() {
124
+ global_ref<VideoCompositionFramesExtractorSync> current;
125
+ {
126
+ std::lock_guard<std::mutex> lock(extractorMutex);
127
+ if (released.test_and_set()) return;
128
+ current = std::move(framesExtractor);
129
+ }
130
+ if (current) current->release();
131
+ }
132
+
133
+ } // namespace RNSkiaVideo
@@ -0,0 +1,46 @@
1
+ #pragma once
2
+
3
+ #include "RNSVHostObject.h"
4
+ #include "VideoComposition.h"
5
+ #include "VideoFrame.h"
6
+ #include <fbjni/fbjni.h>
7
+ #include <jsi/jsi.h>
8
+
9
+ namespace RNSkiaVideo {
10
+ using namespace facebook;
11
+ using namespace jni;
12
+
13
+ struct VideoCompositionFramesExtractorSync
14
+ : public jni::JavaClass<VideoCompositionFramesExtractorSync> {
15
+
16
+ public:
17
+ static constexpr auto kJavaDescriptor =
18
+ "Lcom/azzapp/rnskv/VideoCompositionFramesExtractorSync;";
19
+
20
+ local_ref<VideoCompositionFramesExtractorSync> static create(
21
+ alias_ref<VideoComposition> composition);
22
+
23
+ void start() const;
24
+
25
+ local_ref<JMap<JString, VideoFrame>> decodeCompositionFrames(jdouble time);
26
+
27
+ void release() const;
28
+ };
29
+
30
+ class JSI_EXPORT VideoCompositionFramesExtractorSyncHostObject
31
+ : public RNSVHostObject {
32
+ public:
33
+ VideoCompositionFramesExtractorSyncHostObject(jsi::Runtime& runtime,
34
+ jsi::Object composition);
35
+ ~VideoCompositionFramesExtractorSyncHostObject();
36
+ jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override;
37
+ std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;
38
+
39
+ private:
40
+ global_ref<VideoCompositionFramesExtractorSync> framesExtractor;
41
+ std::mutex extractorMutex;
42
+ std::atomic_flag released = ATOMIC_FLAG_INIT;
43
+ void release();
44
+ };
45
+
46
+ } // namespace RNSkiaVideo
@@ -0,0 +1,318 @@
1
+ #include "RNSVCheckedSizes.h"
2
+ #include "VideoEncoderHostObject.h"
3
+ #include "NativeHardwareBuffer.h"
4
+ #include <EGL/egl.h>
5
+ #include <fbjni/ByteBuffer.h>
6
+ #include <cmath>
7
+ #include <limits>
8
+ #include <unistd.h>
9
+
10
+ namespace RNSkiaVideo {
11
+ using namespace facebook::jni;
12
+
13
+ namespace {
14
+ /** Makes the encoder's EGL context current; restores the caller's (or none). */
15
+ class ScopedEncoderContext {
16
+ public:
17
+ explicit ScopedEncoderContext(const global_ref<VideoEncoder>& encoder)
18
+ : display(eglGetCurrentDisplay()), context(eglGetCurrentContext()),
19
+ draw(eglGetCurrentSurface(EGL_DRAW)), read(eglGetCurrentSurface(EGL_READ)) {
20
+ encoder->makeGLContextCurrent();
21
+ }
22
+ ~ScopedEncoderContext() {
23
+ if (context != EGL_NO_CONTEXT) {
24
+ eglMakeCurrent(display, draw, read, context);
25
+ } else if (auto current = eglGetCurrentDisplay(); current != EGL_NO_DISPLAY) {
26
+ eglMakeCurrent(current, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
27
+ }
28
+ }
29
+ ScopedEncoderContext(const ScopedEncoderContext&) = delete;
30
+ ScopedEncoderContext& operator=(const ScopedEncoderContext&) = delete;
31
+
32
+ private:
33
+ EGLDisplay display;
34
+ EGLContext context;
35
+ EGLSurface draw;
36
+ EGLSurface read;
37
+ };
38
+
39
+ /** Sync-fds handed over by JS; each is closed exactly once. */
40
+ struct OwnedFences {
41
+ std::vector<int> fds;
42
+ ~OwnedFences() {
43
+ for (int fd : fds)
44
+ if (fd >= 0) close(fd);
45
+ }
46
+ };
47
+ } // namespace
48
+
49
+ local_ref<VideoEncoder>
50
+ VideoEncoder::create(std::string& outPath, int width, int height, int frameRate,
51
+ int bitRate, std::optional<std::string> encoderName,
52
+ std::optional<std::string> codec,
53
+ alias_ref<VideoComposition> composition,
54
+ int audioSampleRate, int audioChannelCount,
55
+ int audioBitRate) {
56
+ // Both strings are nullable on the Java side (a null codec means H.264), so
57
+ // they are built as jstring refs rather than handed over as std::string: a
58
+ // ternary mixing std::string and nullptr collapses to std::string, and
59
+ // constructing one from nullptr calls strlen(nullptr).
60
+ auto encoderNameRef = encoderName.has_value()
61
+ ? make_jstring(encoderName.value())
62
+ : local_ref<JString>(nullptr);
63
+ auto codecRef = codec.has_value() ? make_jstring(codec.value())
64
+ : local_ref<JString>(nullptr);
65
+ return newInstance(outPath, width, height, frameRate, bitRate,
66
+ encoderNameRef, codecRef, composition, audioSampleRate,
67
+ audioChannelCount, audioBitRate);
68
+ }
69
+
70
+ bool VideoEncoder::isCodecSupported(const std::string& codec) {
71
+ static const auto method =
72
+ javaClassStatic()->getStaticMethod<jboolean(std::string)>(
73
+ "isCodecSupported");
74
+ return method(javaClassStatic(), codec);
75
+ }
76
+
77
+ void VideoEncoder::prepare() const {
78
+ static const auto prepareMethod = getClass()->getMethod<void()>("prepare");
79
+ prepareMethod(self());
80
+ }
81
+
82
+ void VideoEncoder::makeGLContextCurrent() const {
83
+ static const auto makeGLContextCurrentMethod =
84
+ getClass()->getMethod<void()>("makeGLContextCurrent");
85
+ makeGLContextCurrentMethod(self());
86
+ }
87
+
88
+ void VideoEncoder::encodeFrameRgba(alias_ref<JByteBuffer> pixels, jint width,
89
+ jint height, jint bytesPerRow, jdouble time) const {
90
+ static const auto method =
91
+ getClass()->getMethod<void(alias_ref<JByteBuffer>, jint, jint, jint, jdouble)>("encodeFrameRgba");
92
+ method(self(), pixels, width, height, bytesPerRow, time);
93
+ }
94
+
95
+ void VideoEncoder::encodeTexture(jint texture, jdouble time) const {
96
+ static const auto method = getClass()->getMethod<void(jint, jdouble)>("encodeTexture");
97
+ method(self(), texture, time);
98
+ }
99
+
100
+ void VideoEncoder::release() const {
101
+ static const auto releaseMethod = getClass()->getMethod<void()>("release");
102
+ releaseMethod(self());
103
+ }
104
+
105
+ void VideoEncoder::finishWriting() const {
106
+ static const auto finishWritingMethod =
107
+ getClass()->getMethod<void()>("finishWriting");
108
+ finishWritingMethod(self());
109
+ }
110
+
111
+ VideoEncoderHostObject::VideoEncoderHostObject(
112
+ std::string& outPath, int width, int height, int frameRate, int bitRate,
113
+ std::optional<std::string> encoderName, std::optional<std::string> codec,
114
+ alias_ref<VideoComposition> composition, int audioSampleRate,
115
+ int audioChannelCount, int audioBitRate)
116
+ : width(width), height(height) {
117
+ framesExtractor = make_global(VideoEncoder::create(
118
+ outPath, width, height, frameRate, bitRate, encoderName, codec,
119
+ composition, audioSampleRate, audioChannelCount, audioBitRate));
120
+ }
121
+
122
+ VideoEncoderHostObject::~VideoEncoderHostObject() {
123
+ this->release();
124
+ }
125
+
126
+ std::vector<jsi::PropNameID>
127
+ VideoEncoderHostObject::getPropertyNames(jsi::Runtime& rt) {
128
+ std::vector<jsi::PropNameID> result;
129
+ result.push_back(jsi::PropNameID::forUtf8(rt, std::string("prepare")));
130
+ result.push_back(jsi::PropNameID::forUtf8(rt, std::string("encodeFrame")));
131
+ result.push_back(jsi::PropNameID::forUtf8(rt, std::string("acquireFrameBuffer")));
132
+ result.push_back(jsi::PropNameID::forUtf8(rt, std::string("releaseFrameBuffer")));
133
+ result.push_back(jsi::PropNameID::forUtf8(rt, std::string("finishWriting")));
134
+ result.push_back(jsi::PropNameID::forUtf8(rt, std::string("dispose")));
135
+ return result;
136
+ }
137
+
138
+ // The methods are created once per runtime (see RNSVHostObject):
139
+ // `encodeFrame` is read for every exported frame.
140
+ jsi::Value VideoEncoderHostObject::get(jsi::Runtime& runtime,
141
+ const jsi::PropNameID& propNameId) {
142
+ auto propName = propNameId.utf8(runtime);
143
+ if (propName == "encodeFrame") {
144
+ return getFunction(
145
+ runtime, propName, 2,
146
+ [this](jsi::Runtime& runtime, const jsi::Value& thisValue,
147
+ const jsi::Value* arguments, size_t count) -> jsi::Value {
148
+ std::lock_guard<std::recursive_mutex> lock(encoderMutex);
149
+ if (released.test() || !prepared || finished) {
150
+ throw jsi::JSError(runtime, "Video encoder is not ready to accept frames");
151
+ }
152
+ if (count != 2 || !arguments[0].isObject() || !arguments[1].isNumber()) {
153
+ throw jsi::JSError(runtime, "encodeFrame expects an owned RGBA descriptor and a time");
154
+ }
155
+ auto frame = arguments[0].asObject(runtime);
156
+ const auto kindValue = frame.getProperty(runtime, "kind");
157
+ const auto kind = kindValue.isString() ? kindValue.asString(runtime).utf8(runtime) : "";
158
+ if (kind == "native-buffer") {
159
+ // A lent buffer Skia rendered into; its sync-fds are ours to close.
160
+ OwnedFences fences;
161
+ const auto fenceValues = frame.getProperty(runtime, "fences");
162
+ if (fenceValues.isObject()) {
163
+ auto array = fenceValues.asObject(runtime).asArray(runtime);
164
+ for (size_t i = 0; i < array.size(runtime); ++i) {
165
+ auto value = array.getValueAtIndex(runtime, i);
166
+ if (!value.isBigInt()) throw jsi::JSError(runtime, "Encoder fences must be sync-fd BigInts");
167
+ // WebGPU reports an already signaled fence as -1, as a uint32.
168
+ fences.fds.push_back(static_cast<int>(
169
+ static_cast<uint32_t>(value.asBigInt(runtime).getUint64(runtime))));
170
+ }
171
+ }
172
+ const auto pointerValue = frame.getProperty(runtime, "nativeBuffer");
173
+ if (!pointerValue.isBigInt() || !targets)
174
+ throw jsi::JSError(runtime, "Encoder frame buffer is not the one lent");
175
+ const auto* buffer = reinterpret_cast<const AHardwareBuffer*>(
176
+ static_cast<uintptr_t>(pointerValue.asBigInt(runtime).getUint64(runtime)));
177
+ const auto time = checkedMediaSeconds(arguments[1].asNumber());
178
+ ScopedEncoderContext scope(framesExtractor);
179
+ const auto texture = targets->beginEncode(buffer, fences.fds);
180
+ try {
181
+ framesExtractor->encodeTexture(static_cast<jint>(texture), time);
182
+ } catch (...) {
183
+ targets->finishEncode();
184
+ throw;
185
+ }
186
+ targets->finishEncode();
187
+ return jsi::Value::undefined();
188
+ }
189
+ if (kind != "rgba") {
190
+ throw jsi::JSError(runtime, "Android encoder accepts RGBA pixels or a lent hardware buffer");
191
+ }
192
+ const auto checkedInteger = [&](const char* name) -> int {
193
+ const auto value = frame.getProperty(runtime, name).asNumber();
194
+ if (!std::isfinite(value) || value <= 0 || std::floor(value) != value ||
195
+ value > std::numeric_limits<int>::max()) {
196
+ throw jsi::JSError(runtime, "Invalid encoder RGBA dimensions or stride");
197
+ }
198
+ return static_cast<int>(value);
199
+ };
200
+ const int width = checkedInteger("width");
201
+ const int height = checkedInteger("height");
202
+ const int stride = checkedInteger("bytesPerRow");
203
+ const uint64_t packedStride = static_cast<uint64_t>(width) * 4;
204
+ const uint64_t required = static_cast<uint64_t>(stride) * (height - 1) + packedStride;
205
+ if (static_cast<uint64_t>(stride) < packedStride || required > std::numeric_limits<int>::max()) {
206
+ throw jsi::JSError(runtime, "Invalid encoder RGBA byte layout");
207
+ }
208
+ auto data = frame.getProperty(runtime, "data").asObject(runtime);
209
+ size_t offset = 0;
210
+ size_t length = 0;
211
+ jsi::ArrayBuffer buffer = [&]() {
212
+ if (data.isArrayBuffer(runtime)) {
213
+ auto result = data.getArrayBuffer(runtime);
214
+ length = result.size(runtime);
215
+ return result;
216
+ }
217
+ auto raw = data.getProperty(runtime, "buffer").asObject(runtime).getArrayBuffer(runtime);
218
+ const auto offsetValue = data.getProperty(runtime, "byteOffset").asNumber();
219
+ const auto lengthValue = data.getProperty(runtime, "byteLength").asNumber();
220
+ if (!std::isfinite(offsetValue) || !std::isfinite(lengthValue) || offsetValue < 0 ||
221
+ lengthValue < 0 || std::floor(offsetValue) != offsetValue ||
222
+ std::floor(lengthValue) != lengthValue || offsetValue > raw.size(runtime) ||
223
+ lengthValue > raw.size(runtime) - offsetValue) {
224
+ throw jsi::JSError(runtime, "Invalid encoder RGBA byte view");
225
+ }
226
+ offset = static_cast<size_t>(offsetValue);
227
+ length = static_cast<size_t>(lengthValue);
228
+ return raw;
229
+ }();
230
+ if (length < required) {
231
+ throw jsi::JSError(runtime, "Encoder RGBA data is too short");
232
+ }
233
+ auto javaPixels = JByteBuffer::wrapBytes(buffer.data(runtime) + offset, required);
234
+ framesExtractor->encodeFrameRgba(javaPixels, width, height, stride, checkedMediaSeconds(arguments[1].asNumber()));
235
+ return jsi::Value::undefined();
236
+ });
237
+ } else if (propName == "acquireFrameBuffer") {
238
+ return getFunction(
239
+ runtime, propName, 0,
240
+ [this](jsi::Runtime& runtime, const jsi::Value& thisValue,
241
+ const jsi::Value* arguments, size_t count) -> jsi::Value {
242
+ std::lock_guard<std::recursive_mutex> lock(encoderMutex);
243
+ if (released.test() || !prepared || finished) {
244
+ throw jsi::JSError(runtime, "Encoder not prepared");
245
+ }
246
+ // The buffers are imported by the same Dawn device as decoded AHBs.
247
+ if (!NativeHardwareBuffer::isEnabled()) {
248
+ throw jsi::JSError(runtime, "Android GPU export requires AHardwareBuffer interop");
249
+ }
250
+ if (!targets) targets = std::make_unique<EncoderTargets>(width, height);
251
+ auto* buffer = targets->lend();
252
+ return jsi::BigInt::fromUint64(runtime, reinterpret_cast<uintptr_t>(buffer));
253
+ });
254
+ } else if (propName == "releaseFrameBuffer") {
255
+ return getFunction(
256
+ runtime, propName, 0,
257
+ [this](jsi::Runtime& runtime, const jsi::Value& thisValue,
258
+ const jsi::Value* arguments, size_t count) -> jsi::Value {
259
+ std::lock_guard<std::recursive_mutex> lock(encoderMutex);
260
+ if (targets) targets->giveBack();
261
+ return jsi::Value::undefined();
262
+ });
263
+ } else if (propName == "prepare") {
264
+ return getFunction(
265
+ runtime, propName, 0,
266
+ [this](jsi::Runtime& runtime, const jsi::Value& thisValue,
267
+ const jsi::Value* arguments, size_t count) -> jsi::Value {
268
+ std::lock_guard<std::recursive_mutex> lock(encoderMutex);
269
+ if (!released.test() && !prepared) {
270
+ framesExtractor->prepare();
271
+ prepared = true;
272
+ }
273
+ return jsi::Value::undefined();
274
+ });
275
+ } else if (propName == "finishWriting") {
276
+ return getFunction(
277
+ runtime, propName, 0,
278
+ [this](jsi::Runtime& runtime, const jsi::Value& thisValue,
279
+ const jsi::Value* arguments, size_t count) -> jsi::Value {
280
+ std::lock_guard<std::recursive_mutex> lock(encoderMutex);
281
+ if (!released.test() && prepared && !finished) {
282
+ framesExtractor->finishWriting();
283
+ finished = true;
284
+ }
285
+ return jsi::Value::undefined();
286
+ });
287
+ }
288
+ if (propName == "dispose") {
289
+ return getFunction(
290
+ runtime, propName, 0,
291
+ [this](jsi::Runtime& runtime, const jsi::Value& thisValue,
292
+ const jsi::Value* arguments, size_t count) -> jsi::Value {
293
+ this->release();
294
+ return jsi::Value::undefined();
295
+ });
296
+ }
297
+ return jsi::Value::undefined();
298
+ }
299
+
300
+ void VideoEncoderHostObject::release() {
301
+ std::lock_guard<std::recursive_mutex> lock(encoderMutex);
302
+ if (!released.test_and_set()) {
303
+ if (targets && !targets->empty()) {
304
+ // GL names belong to the encoder context; free them before it goes.
305
+ // Otherwise the destructor still releases buffers and reservations.
306
+ try {
307
+ ScopedEncoderContext scope(framesExtractor);
308
+ targets->clear();
309
+ } catch (...) {
310
+ }
311
+ }
312
+ targets.reset();
313
+ framesExtractor->release();
314
+ framesExtractor = nullptr;
315
+ }
316
+ }
317
+
318
+ } // namespace RNSkiaVideo
@@ -0,0 +1,76 @@
1
+ #pragma once
2
+
3
+ #include "EncoderTargets.h"
4
+ #include "RNSVHostObject.h"
5
+ #include "VideoComposition.h"
6
+ #include <memory>
7
+ #include <mutex>
8
+ #include <fbjni/ByteBuffer.h>
9
+ #include <fbjni/fbjni.h>
10
+ #include <jsi/jsi.h>
11
+
12
+ namespace RNSkiaVideo {
13
+ using namespace facebook;
14
+ using namespace jni;
15
+
16
+ struct VideoEncoder : public jni::JavaClass<VideoEncoder> {
17
+
18
+ public:
19
+ static constexpr auto kJavaDescriptor = "Lcom/azzapp/rnskv/VideoEncoder;";
20
+
21
+ local_ref<VideoEncoder> static create(std::string& outPath, int width,
22
+ int height, int frameRate, int bitRate,
23
+ std::optional<std::string> encoderName,
24
+ std::optional<std::string> codec,
25
+ alias_ref<VideoComposition> composition,
26
+ int audioSampleRate,
27
+ int audioChannelCount,
28
+ int audioBitRate);
29
+
30
+ /**
31
+ * Whether the device has an encoder for the given codec ("h264" or "hevc").
32
+ */
33
+ static bool isCodecSupported(const std::string& codec);
34
+
35
+ void prepare() const;
36
+
37
+ void makeGLContextCurrent() const;
38
+
39
+ void encodeFrameRgba(alias_ref<JByteBuffer> pixels, jint width, jint height,
40
+ jint bytesPerRow, jdouble time) const;
41
+
42
+ /** A GL_TEXTURE_2D of the encoder's context, e.g. an imported buffer. */
43
+ void encodeTexture(jint texture, jdouble time) const;
44
+
45
+ void finishWriting() const;
46
+
47
+ void release() const;
48
+ };
49
+
50
+ class JSI_EXPORT VideoEncoderHostObject : public RNSVHostObject {
51
+ public:
52
+ VideoEncoderHostObject(std::string& outPath, int width, int height,
53
+ int frameRate, int bitRate,
54
+ std::optional<std::string> encoderName,
55
+ std::optional<std::string> codec,
56
+ alias_ref<VideoComposition> composition,
57
+ int audioSampleRate, int audioChannelCount,
58
+ int audioBitRate);
59
+ ~VideoEncoderHostObject() override;
60
+ jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override;
61
+ std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;
62
+
63
+ private:
64
+ global_ref<VideoEncoder> framesExtractor;
65
+ // GPU export: buffers Skia renders into, sampled by the encoder's context.
66
+ std::unique_ptr<EncoderTargets> targets;
67
+ int width;
68
+ int height;
69
+ std::recursive_mutex encoderMutex;
70
+ bool prepared = false;
71
+ bool finished = false;
72
+ std::atomic_flag released = ATOMIC_FLAG_INIT;
73
+ void release();
74
+ };
75
+
76
+ } // namespace RNSkiaVideo