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.
- package/LICENSE +22 -0
- package/README.md +303 -2
- package/android/BUILD_VALIDATION.md +62 -0
- package/android/CMakeLists.txt +99 -0
- package/android/build-validation.json +262 -0
- package/android/build.gradle +185 -0
- package/android/cpp/EncoderTargets.cpp +226 -0
- package/android/cpp/EncoderTargets.h +70 -0
- package/android/cpp/JNIHelpers.cpp +15 -0
- package/android/cpp/JNIHelpers.h +20 -0
- package/android/cpp/NativeEventDispatcher.cpp +59 -0
- package/android/cpp/NativeEventDispatcher.h +31 -0
- package/android/cpp/NativeHardwareBuffer.cpp +397 -0
- package/android/cpp/NativeHardwareBuffer.h +34 -0
- package/android/cpp/NativeRgbaBuffer.cpp +45 -0
- package/android/cpp/NativeRgbaBuffer.h +9 -0
- package/android/cpp/ReturnedVideoFrames.h +37 -0
- package/android/cpp/RgbaFrameLease.h +53 -0
- package/android/cpp/VideoCapabilities.cpp +87 -0
- package/android/cpp/VideoCapabilities.h +44 -0
- package/android/cpp/VideoComposition.cpp +170 -0
- package/android/cpp/VideoComposition.h +36 -0
- package/android/cpp/VideoCompositionFramesExtractor.cpp +82 -0
- package/android/cpp/VideoCompositionFramesExtractor.h +48 -0
- package/android/cpp/VideoCompositionFramesExtractorHostObject.cpp +230 -0
- package/android/cpp/VideoCompositionFramesExtractorHostObject.h +42 -0
- package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.cpp +133 -0
- package/android/cpp/VideoCompositionFramesExtractorSyncHostObject.h +46 -0
- package/android/cpp/VideoEncoderHostObject.cpp +318 -0
- package/android/cpp/VideoEncoderHostObject.h +76 -0
- package/android/cpp/VideoFrame.cpp +293 -0
- package/android/cpp/VideoFrame.h +27 -0
- package/android/cpp/VideoPlayer.cpp +96 -0
- package/android/cpp/VideoPlayer.h +52 -0
- package/android/cpp/VideoPlayerHostObject.cpp +225 -0
- package/android/cpp/VideoPlayerHostObject.h +43 -0
- package/android/cpp/cpp-adapter.cpp +316 -0
- package/android/gradle.properties +5 -0
- package/android/instrumentation-inventory.json +178 -0
- package/android/proguard-rules.pro +4 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/azzapp/rnskv/AudioCompositionExporter.java +537 -0
- package/android/src/main/java/com/azzapp/rnskv/AudioCompositionPlayer.java +321 -0
- package/android/src/main/java/com/azzapp/rnskv/BufferLifetime.java +55 -0
- package/android/src/main/java/com/azzapp/rnskv/DecodeRequests.java +100 -0
- package/android/src/main/java/com/azzapp/rnskv/DecoderWindow.java +55 -0
- package/android/src/main/java/com/azzapp/rnskv/DisplayVsyncPeriod.java +72 -0
- package/android/src/main/java/com/azzapp/rnskv/EGLResourcesHolder.java +132 -0
- package/android/src/main/java/com/azzapp/rnskv/EGLUtils.java +225 -0
- package/android/src/main/java/com/azzapp/rnskv/FrameSize.java +50 -0
- package/android/src/main/java/com/azzapp/rnskv/GLFrameExtractor.java +510 -0
- package/android/src/main/java/com/azzapp/rnskv/JNIHelpers.java +12 -0
- package/android/src/main/java/com/azzapp/rnskv/NativeEventDispatcher.java +25 -0
- package/android/src/main/java/com/azzapp/rnskv/NativeHardwareBuffer.java +62 -0
- package/android/src/main/java/com/azzapp/rnskv/NativeRgbaBuffer.java +43 -0
- package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoModule.java +45 -0
- package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoPackage.java +43 -0
- package/android/src/main/java/com/azzapp/rnskv/ReactNativeSkiaVideoSpec.java +9 -0
- package/android/src/main/java/com/azzapp/rnskv/RgbaBufferAllocator.java +114 -0
- package/android/src/main/java/com/azzapp/rnskv/RgbaLayout.java +33 -0
- package/android/src/main/java/com/azzapp/rnskv/SurfaceOutputPolicy.java +13 -0
- package/android/src/main/java/com/azzapp/rnskv/TextureRenderer.java +150 -0
- package/android/src/main/java/com/azzapp/rnskv/TimeHelpers.java +11 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCapabilities.java +354 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoComposition.java +147 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionDecoder.java +779 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +463 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractorSync.java +241 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionItemDecoder.java +492 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoEncoder.java +639 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoFrame.java +74 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoPlayer.java +361 -0
- package/cpp/RNSVCheckedSizes.h +54 -0
- package/cpp/RNSVEventEmitter.cpp +199 -0
- package/cpp/RNSVEventEmitter.h +31 -0
- package/cpp/RNSVHostObject.cpp +79 -0
- package/cpp/RNSVHostObject.h +67 -0
- package/cpp/RNSVMemoryBudget.cpp +68 -0
- package/cpp/RNSVMemoryBudget.h +100 -0
- package/cpp/RNSVRuntimeLifecycleMonitor.cpp +88 -0
- package/cpp/RNSVRuntimeLifecycleMonitor.h +33 -0
- package/docs/ANDROID_DEVICE_306.md +95 -0
- package/docs/ANDROID_EXPORT_CACHE_306.md +48 -0
- package/docs/ANDROID_MEMORY_RETENTION_306.md +67 -0
- package/docs/BENCHMARKS.md +368 -0
- package/docs/IOS_DEVICE_TESTS.md +115 -0
- package/docs/IOS_SIMULATOR_TESTS.md +74 -0
- package/docs/MIGRATION.md +82 -0
- package/docs/NATIVE_MEMORY_INVESTIGATION_306.md +88 -0
- package/docs/PERFORMANCE_AB_IPHONE_303.md +196 -0
- package/docs/PERFORMANCE_AB_SIMULATOR.md +135 -0
- package/docs/PERFORMANCE_AB_SOAK_SIMULATOR.md +84 -0
- package/docs/PERFORMANCE_IPHONE_306_OPTIMIZATION.md +76 -0
- package/docs/PERFORMANCE_SIMULATOR.md +89 -0
- package/docs/PERFORMANCE_SIMULATOR_INITIAL.md +96 -0
- package/docs/PERFORMANCE_SOAK_IPHONE_303.md +41 -0
- package/docs/UPSTREAM_NOTES.md +188 -0
- package/docs/assets/banner.svg +26 -0
- package/examples/webgpu-overlay.ts +147 -0
- package/ios/AVAssetTrackUtils.h +24 -0
- package/ios/AudioCompositionUtils.h +27 -0
- package/ios/AudioCompositionUtils.mm +113 -0
- package/ios/DecoderWindow.h +52 -0
- package/ios/NativeResourceStats.h +32 -0
- package/ios/RNSVColorSpace.h +50 -0
- package/ios/RNSVJSIUtils.h +46 -0
- package/ios/RNSVScopedPixelBuffer.h +17 -0
- package/ios/RNSVVideoPlayer.h +46 -0
- package/ios/RNSVVideoPlayer.mm +410 -0
- package/ios/ReactNativeSkiaVideo.h +8 -0
- package/ios/ReactNativeSkiaVideo.mm +310 -0
- package/ios/SeekPolicy.h +27 -0
- package/ios/VideoComposition.h +148 -0
- package/ios/VideoCompositionFramesExtractorHostObject.h +105 -0
- package/ios/VideoCompositionFramesExtractorHostObject.mm +686 -0
- package/ios/VideoCompositionFramesExtractorSyncHostObject.h +37 -0
- package/ios/VideoCompositionFramesExtractorSyncHostObject.mm +168 -0
- package/ios/VideoCompositionItemDecoder.h +60 -0
- package/ios/VideoCompositionItemDecoder.mm +480 -0
- package/ios/VideoEncoderHostObject.h +83 -0
- package/ios/VideoEncoderHostObject.mm +706 -0
- package/ios/VideoFrame.h +66 -0
- package/ios/VideoFrame.mm +108 -0
- package/ios/VideoPlayerHostObject.h +52 -0
- package/ios/VideoPlayerHostObject.mm +332 -0
- package/lib/module/NativeReactNativeSkiaVideo.js +5 -0
- package/lib/module/NativeReactNativeSkiaVideo.js.map +1 -0
- package/lib/module/RNSkiaVideoModule.js +20 -0
- package/lib/module/RNSkiaVideoModule.js.map +1 -0
- package/lib/module/canvas.js +18 -0
- package/lib/module/canvas.js.map +1 -0
- package/lib/module/exportCache.js +22 -0
- package/lib/module/exportCache.js.map +1 -0
- package/lib/module/exportFrames.js +338 -0
- package/lib/module/exportFrames.js.map +1 -0
- package/lib/module/exportQueue.js +52 -0
- package/lib/module/exportQueue.js.map +1 -0
- package/lib/module/exportTarget.js +137 -0
- package/lib/module/exportTarget.js.map +1 -0
- package/lib/module/exportVideoComposition.js +492 -0
- package/lib/module/exportVideoComposition.js.map +1 -0
- package/lib/module/frameInterop.js +532 -0
- package/lib/module/frameInterop.js.map +1 -0
- package/lib/module/gpu.js +222 -0
- package/lib/module/gpu.js.map +1 -0
- package/lib/module/gpuDevice.js +54 -0
- package/lib/module/gpuDevice.js.map +1 -0
- package/lib/module/index.js +36 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/memory.js +38 -0
- package/lib/module/memory.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/previewLifecycle.js +52 -0
- package/lib/module/previewLifecycle.js.map +1 -0
- package/lib/module/runtimeYield.js +10 -0
- package/lib/module/runtimeYield.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/useEventListener.js +18 -0
- package/lib/module/utils/useEventListener.js.map +1 -0
- package/lib/module/videoCompositionPlayer.js +368 -0
- package/lib/module/videoCompositionPlayer.js.map +1 -0
- package/lib/module/videoFrameImage.js +185 -0
- package/lib/module/videoFrameImage.js.map +1 -0
- package/lib/module/videoPlayer.js +172 -0
- package/lib/module/videoPlayer.js.map +1 -0
- package/lib/module/yuvBlit.js +299 -0
- package/lib/module/yuvBlit.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts +7 -0
- package/lib/typescript/src/NativeReactNativeSkiaVideo.d.ts.map +1 -0
- package/lib/typescript/src/RNSkiaVideoModule.d.ts +4 -0
- package/lib/typescript/src/RNSkiaVideoModule.d.ts.map +1 -0
- package/lib/typescript/src/canvas.d.ts +4 -0
- package/lib/typescript/src/canvas.d.ts.map +1 -0
- package/lib/typescript/src/exportCache.d.ts +6 -0
- package/lib/typescript/src/exportCache.d.ts.map +1 -0
- package/lib/typescript/src/exportFrames.d.ts +57 -0
- package/lib/typescript/src/exportFrames.d.ts.map +1 -0
- package/lib/typescript/src/exportQueue.d.ts +5 -0
- package/lib/typescript/src/exportQueue.d.ts.map +1 -0
- package/lib/typescript/src/exportTarget.d.ts +28 -0
- package/lib/typescript/src/exportTarget.d.ts.map +1 -0
- package/lib/typescript/src/exportVideoComposition.d.ts +68 -0
- package/lib/typescript/src/exportVideoComposition.d.ts.map +1 -0
- package/lib/typescript/src/frameInterop.d.ts +17 -0
- package/lib/typescript/src/frameInterop.d.ts.map +1 -0
- package/lib/typescript/src/gpu.d.ts +39 -0
- package/lib/typescript/src/gpu.d.ts.map +1 -0
- package/lib/typescript/src/gpuDevice.d.ts +11 -0
- package/lib/typescript/src/gpuDevice.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +21 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/memory.d.ts +25 -0
- package/lib/typescript/src/memory.d.ts.map +1 -0
- package/lib/typescript/src/previewLifecycle.d.ts +22 -0
- package/lib/typescript/src/previewLifecycle.d.ts.map +1 -0
- package/lib/typescript/src/runtimeYield.d.ts +4 -0
- package/lib/typescript/src/runtimeYield.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +631 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/utils/useEventListener.d.ts +5 -0
- package/lib/typescript/src/utils/useEventListener.d.ts.map +1 -0
- package/lib/typescript/src/videoCompositionPlayer.d.ts +105 -0
- package/lib/typescript/src/videoCompositionPlayer.d.ts.map +1 -0
- package/lib/typescript/src/videoFrameImage.d.ts +60 -0
- package/lib/typescript/src/videoFrameImage.d.ts.map +1 -0
- package/lib/typescript/src/videoPlayer.d.ts +99 -0
- package/lib/typescript/src/videoPlayer.d.ts.map +1 -0
- package/lib/typescript/src/yuvBlit.d.ts +50 -0
- package/lib/typescript/src/yuvBlit.d.ts.map +1 -0
- package/package.json +155 -4
- package/patches/react-native-skia+3.7.0.patch +502 -0
- package/react-native-skia-webgpu-video.podspec +35 -0
- package/scripts/apply-skia-canvas-patch.mjs +48 -0
- package/scripts/skia-canvas-patch.json +291 -0
- package/src/NativeReactNativeSkiaVideo.ts +8 -0
- package/src/RNSkiaVideoModule.ts +27 -0
- package/src/canvas.ts +22 -0
- package/src/exportCache.ts +27 -0
- package/src/exportFrames.ts +421 -0
- package/src/exportQueue.ts +65 -0
- package/src/exportTarget.ts +179 -0
- package/src/exportVideoComposition.ts +718 -0
- package/src/frameInterop.ts +684 -0
- package/src/gpu.ts +290 -0
- package/src/gpuDevice.ts +80 -0
- package/src/index.tsx +45 -0
- package/src/memory.ts +48 -0
- package/src/previewLifecycle.ts +60 -0
- package/src/runtimeYield.ts +6 -0
- package/src/types.ts +661 -0
- package/src/utils/useEventListener.ts +22 -0
- package/src/videoCompositionPlayer.ts +568 -0
- package/src/videoFrameImage.ts +205 -0
- package/src/videoPlayer.ts +318 -0
- package/src/yuvBlit.ts +331 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#import "ReactNativeSkiaVideo.h"
|
|
2
|
+
|
|
3
|
+
#import <React/RCTBridge+Private.h>
|
|
4
|
+
#import <React/RCTBridge.h>
|
|
5
|
+
#import <React/RCTCallInvoker.h>
|
|
6
|
+
#import <React/RCTUtils.h>
|
|
7
|
+
#import <ReactCommon/RCTTurboModule.h>
|
|
8
|
+
#import <jsi/jsi.h>
|
|
9
|
+
|
|
10
|
+
#import "RNSVJSIUtils.h"
|
|
11
|
+
#import "NativeResourceStats.h"
|
|
12
|
+
#include "RNSVMemoryBudget.h"
|
|
13
|
+
#import "VideoComposition.h"
|
|
14
|
+
#import "VideoCompositionFramesExtractorHostObject.h"
|
|
15
|
+
#import "VideoCompositionFramesExtractorSyncHostObject.h"
|
|
16
|
+
#import "VideoEncoderHostObject.h"
|
|
17
|
+
#include "RNSVCheckedSizes.h"
|
|
18
|
+
#import "VideoPlayerHostObject.h"
|
|
19
|
+
|
|
20
|
+
// In bridgeless mode `self.bridge` is an RCTBridgeProxy; its `runtime`
|
|
21
|
+
// accessor is not declared in public headers.
|
|
22
|
+
@interface RCTBridge (JSIRuntime)
|
|
23
|
+
- (void*)runtime;
|
|
24
|
+
@end
|
|
25
|
+
|
|
26
|
+
@implementation ReactNativeSkiaVideo
|
|
27
|
+
|
|
28
|
+
@synthesize bridge = _bridge;
|
|
29
|
+
@synthesize callInvoker = _callInvoker;
|
|
30
|
+
|
|
31
|
+
RCT_EXPORT_MODULE()
|
|
32
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(install) {
|
|
33
|
+
using namespace facebook;
|
|
34
|
+
using namespace RNSkiaVideo;
|
|
35
|
+
|
|
36
|
+
auto jsiRuntime = reinterpret_cast<jsi::Runtime*>(self.bridge.runtime);
|
|
37
|
+
if (jsiRuntime == nullptr) {
|
|
38
|
+
return @false;
|
|
39
|
+
}
|
|
40
|
+
std::shared_ptr<react::CallInvoker> jsCallInvoker =
|
|
41
|
+
self.callInvoker.callInvoker;
|
|
42
|
+
if (jsCallInvoker == nullptr) {
|
|
43
|
+
return @false;
|
|
44
|
+
}
|
|
45
|
+
auto& runtime = *jsiRuntime;
|
|
46
|
+
auto RNSVModule = jsi::Object(runtime);
|
|
47
|
+
|
|
48
|
+
auto createVideoPlayer = jsi::Function::createFromHostFunction(
|
|
49
|
+
runtime, jsi::PropNameID::forAscii(runtime, "createVideoPlayer"), 3,
|
|
50
|
+
[jsCallInvoker](jsi::Runtime& runtime, const jsi::Value& thisValue,
|
|
51
|
+
const jsi::Value* arguments, size_t count) -> jsi::Value {
|
|
52
|
+
if (count < 1 || !arguments[0].isString()) {
|
|
53
|
+
throw jsi::JSError(runtime,
|
|
54
|
+
"ReactNativeSkiaVideo.createVideoPlayer(."
|
|
55
|
+
".) expects two arguments (string, object)!");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
NSString* urlStr;
|
|
59
|
+
try {
|
|
60
|
+
urlStr = [NSString stringWithUTF8String:arguments[0]
|
|
61
|
+
.asString(runtime)
|
|
62
|
+
.utf8(runtime)
|
|
63
|
+
.c_str()];
|
|
64
|
+
} catch (NSError* error) {
|
|
65
|
+
throw jsi::JSError(
|
|
66
|
+
runtime, "SkiaVideo.createRNSVPlayer(..) could not parse url");
|
|
67
|
+
}
|
|
68
|
+
CGSize resolution = CGSize();
|
|
69
|
+
if (count >= 2 && arguments[1].isObject()) {
|
|
70
|
+
auto res = arguments[1].asObject(runtime);
|
|
71
|
+
resolution.width = checkedPositiveInt(res.getProperty(runtime, "width").asNumber(), "decode width");
|
|
72
|
+
resolution.height = checkedPositiveInt(res.getProperty(runtime, "height").asNumber(), "decode height");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// { textureMode?: 'copy' | 'direct' } — copy is the default.
|
|
76
|
+
bool directTexture = false;
|
|
77
|
+
if (count >= 3 && arguments[2].isObject()) {
|
|
78
|
+
auto options = arguments[2].asObject(runtime);
|
|
79
|
+
if (options.hasProperty(runtime, "textureMode")) {
|
|
80
|
+
auto mode = options.getProperty(runtime, "textureMode");
|
|
81
|
+
if (mode.isString()) {
|
|
82
|
+
directTexture = mode.asString(runtime).utf8(runtime) == "direct";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
NSURL* url = [NSURL URLWithString:urlStr];
|
|
88
|
+
|
|
89
|
+
auto instance = std::make_shared<VideoPlayerHostObject>(
|
|
90
|
+
runtime, jsCallInvoker, url, resolution, directTexture);
|
|
91
|
+
instance->initialize(url, resolution);
|
|
92
|
+
return jsi::Object::createFromHostObject(runtime, instance);
|
|
93
|
+
});
|
|
94
|
+
RNSVModule.setProperty(runtime, "createVideoPlayer",
|
|
95
|
+
std::move(createVideoPlayer));
|
|
96
|
+
|
|
97
|
+
runtime.global().setProperty(runtime, "RNSkiaVideo", RNSVModule);
|
|
98
|
+
|
|
99
|
+
auto createVideoCompositionFramesExtractor =
|
|
100
|
+
jsi::Function::createFromHostFunction(
|
|
101
|
+
runtime,
|
|
102
|
+
jsi::PropNameID::forAscii(runtime,
|
|
103
|
+
"createVideoCompositionFramesExtractor"),
|
|
104
|
+
1,
|
|
105
|
+
[jsCallInvoker](jsi::Runtime& runtime, const jsi::Value& thisValue,
|
|
106
|
+
const jsi::Value* arguments,
|
|
107
|
+
size_t count) -> jsi::Value {
|
|
108
|
+
if (count != 1 || !arguments[0].isObject()) {
|
|
109
|
+
throw jsi::JSError(runtime,
|
|
110
|
+
"ReactNativeSkiaVideo."
|
|
111
|
+
"createVideoCompositionFramesExtractor(.."
|
|
112
|
+
") expects one arguments (object)!");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
jsi::Object jsObject = arguments[0].asObject(runtime);
|
|
116
|
+
auto videoComposition = VideoComposition::fromJS(runtime, jsObject);
|
|
117
|
+
auto instance =
|
|
118
|
+
std::make_shared<VideoCompositionFramesExtractorHostObject>(
|
|
119
|
+
runtime, jsCallInvoker, videoComposition);
|
|
120
|
+
return jsi::Object::createFromHostObject(runtime, instance);
|
|
121
|
+
});
|
|
122
|
+
RNSVModule.setProperty(runtime, "createVideoCompositionFramesExtractor",
|
|
123
|
+
std::move(createVideoCompositionFramesExtractor));
|
|
124
|
+
|
|
125
|
+
auto createVideoCompositionFramesExtractorSync =
|
|
126
|
+
jsi::Function::createFromHostFunction(
|
|
127
|
+
runtime,
|
|
128
|
+
jsi::PropNameID::forAscii(
|
|
129
|
+
runtime, "createVideoCompositionFramesExtractorSync"),
|
|
130
|
+
1,
|
|
131
|
+
[](jsi::Runtime& runtime, const jsi::Value& thisValue,
|
|
132
|
+
const jsi::Value* arguments, size_t count) -> jsi::Value {
|
|
133
|
+
if (count != 1 || !arguments[0].isObject()) {
|
|
134
|
+
throw jsi::JSError(runtime,
|
|
135
|
+
"ReactNativeSkiaVideo."
|
|
136
|
+
"createVideoCompositionFramesExtractorSync(..)"
|
|
137
|
+
" expects one arguments (object)!");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
jsi::Object jsObject = arguments[0].asObject(runtime);
|
|
141
|
+
auto videoComposition = VideoComposition::fromJS(runtime, jsObject);
|
|
142
|
+
auto instance =
|
|
143
|
+
std::make_shared<VideoCompositionFramesExtractorSyncHostObject>(
|
|
144
|
+
videoComposition);
|
|
145
|
+
return jsi::Object::createFromHostObject(runtime, instance);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
RNSVModule.setProperty(runtime, "createVideoCompositionFramesExtractorSync",
|
|
149
|
+
std::move(createVideoCompositionFramesExtractorSync));
|
|
150
|
+
|
|
151
|
+
auto createVideoEncoder = jsi::Function::createFromHostFunction(
|
|
152
|
+
runtime, jsi::PropNameID::forAscii(runtime, "createVideoEncoder"), 2,
|
|
153
|
+
[](jsi::Runtime& runtime, const jsi::Value& thisValue,
|
|
154
|
+
const jsi::Value* arguments, size_t count) -> jsi::Value {
|
|
155
|
+
if (count < 1 || !arguments[0].isObject()) {
|
|
156
|
+
throw jsi::JSError(
|
|
157
|
+
runtime,
|
|
158
|
+
"ReactNativeSkiaVideo.createVideoEncoder(..) expects an options "
|
|
159
|
+
"object and an optional composition object!");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
auto options = arguments[0].asObject(runtime);
|
|
163
|
+
auto outPath = options.getProperty(runtime, "outPath")
|
|
164
|
+
.asString(runtime)
|
|
165
|
+
.utf8(runtime);
|
|
166
|
+
int width = checkedPositiveInt(options.getProperty(runtime, "width").asNumber(), "encoder width");
|
|
167
|
+
int height = checkedPositiveInt(options.getProperty(runtime, "height").asNumber(), "encoder height");
|
|
168
|
+
int frameRate = checkedPositiveInt(options.getProperty(runtime, "frameRate").asNumber(), "encoder frame rate");
|
|
169
|
+
int bitRate = checkedPositiveInt(options.getProperty(runtime, "bitRate").asNumber(), "encoder bitrate");
|
|
170
|
+
std::string codec = "h264";
|
|
171
|
+
if (options.hasProperty(runtime, "codec")) {
|
|
172
|
+
auto value = options.getProperty(runtime, "codec");
|
|
173
|
+
if (value.isString()) {
|
|
174
|
+
codec = value.asString(runtime).utf8(runtime);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
int audioBitRate = 128000;
|
|
178
|
+
int audioSampleRate = 44100;
|
|
179
|
+
int audioChannelCount = 2;
|
|
180
|
+
if (options.hasProperty(runtime, "audioBitRate")) {
|
|
181
|
+
auto value = options.getProperty(runtime, "audioBitRate");
|
|
182
|
+
if (value.isNumber()) {
|
|
183
|
+
audioBitRate = checkedPositiveInt(value.asNumber(), "audio bitrate");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (options.hasProperty(runtime, "audioSampleRate")) {
|
|
187
|
+
auto value = options.getProperty(runtime, "audioSampleRate");
|
|
188
|
+
if (value.isNumber()) {
|
|
189
|
+
audioSampleRate = checkedPositiveInt(value.asNumber(), "audio sample rate");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (options.hasProperty(runtime, "audioChannelCount")) {
|
|
193
|
+
auto value = options.getProperty(runtime, "audioChannelCount");
|
|
194
|
+
if (value.isNumber()) {
|
|
195
|
+
audioChannelCount = checkedPositiveInt(value.asNumber(), "audio channel count");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
std::shared_ptr<VideoComposition> composition = nullptr;
|
|
200
|
+
if (count >= 2 && arguments[1].isObject()) {
|
|
201
|
+
auto jsComposition = arguments[1].asObject(runtime);
|
|
202
|
+
composition = VideoComposition::fromJS(runtime, jsComposition);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// encoderMode: 'copy' (default) | 'direct'
|
|
206
|
+
bool directEncoder = false;
|
|
207
|
+
if (options.hasProperty(runtime, "encoderMode")) {
|
|
208
|
+
auto value = options.getProperty(runtime, "encoderMode");
|
|
209
|
+
if (value.isString()) {
|
|
210
|
+
directEncoder = value.asString(runtime).utf8(runtime) == "direct";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
auto instance = std::make_shared<VideoEncoderHostObject>(
|
|
215
|
+
outPath, width, height, frameRate, bitRate, codec, audioBitRate,
|
|
216
|
+
audioSampleRate, audioChannelCount, composition, directEncoder);
|
|
217
|
+
return jsi::Object::createFromHostObject(runtime, instance);
|
|
218
|
+
});
|
|
219
|
+
RNSVModule.setProperty(runtime, "createVideoEncoder",
|
|
220
|
+
std::move(createVideoEncoder));
|
|
221
|
+
|
|
222
|
+
auto isEncodingSupported = jsi::Function::createFromHostFunction(
|
|
223
|
+
runtime, jsi::PropNameID::forAscii(runtime, "isEncodingSupported"), 1,
|
|
224
|
+
[](jsi::Runtime& runtime, const jsi::Value& thisValue,
|
|
225
|
+
const jsi::Value* arguments, size_t count) -> jsi::Value {
|
|
226
|
+
if (count < 1 || !arguments[0].isString()) {
|
|
227
|
+
throw jsi::JSError(runtime,
|
|
228
|
+
"ReactNativeSkiaVideo.isEncodingSupported(..) "
|
|
229
|
+
"expects a codec name!");
|
|
230
|
+
}
|
|
231
|
+
auto codec = arguments[0].asString(runtime).utf8(runtime);
|
|
232
|
+
return jsi::Value(VideoEncoderHostObject::isCodecSupported(codec));
|
|
233
|
+
});
|
|
234
|
+
RNSVModule.setProperty(runtime, "isEncodingSupported",
|
|
235
|
+
std::move(isEncodingSupported));
|
|
236
|
+
|
|
237
|
+
// Worklet runtime threads never drain their autorelease pool, so any
|
|
238
|
+
// per-frame ObjC garbage created by JS code running there (e.g. the Metal
|
|
239
|
+
// command buffers autoreleased by Skia's surface.flush) accumulates for the
|
|
240
|
+
// lifetime of the app. This lets JS run a block of work inside a pool.
|
|
241
|
+
auto runWithAutoreleasePool = jsi::Function::createFromHostFunction(
|
|
242
|
+
runtime, jsi::PropNameID::forAscii(runtime, "runWithAutoreleasePool"), 1,
|
|
243
|
+
[](jsi::Runtime& runtime, const jsi::Value& thisValue,
|
|
244
|
+
const jsi::Value* arguments, size_t count) -> jsi::Value {
|
|
245
|
+
if (count < 1 || !arguments[0].isObject() ||
|
|
246
|
+
!arguments[0].asObject(runtime).isFunction(runtime)) {
|
|
247
|
+
throw jsi::JSError(runtime,
|
|
248
|
+
"ReactNativeSkiaVideo.runWithAutoreleasePool(..) "
|
|
249
|
+
"expects a function!");
|
|
250
|
+
}
|
|
251
|
+
auto fn = arguments[0].asObject(runtime).asFunction(runtime);
|
|
252
|
+
@autoreleasepool {
|
|
253
|
+
return fn.call(runtime);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
RNSVModule.setProperty(runtime, "runWithAutoreleasePool",
|
|
257
|
+
std::move(runWithAutoreleasePool));
|
|
258
|
+
|
|
259
|
+
MemoryBudget::install(runtime, RNSVModule);
|
|
260
|
+
RNSVModule.setProperty(runtime, "getResourceStats",
|
|
261
|
+
jsi::Function::createFromHostFunction(
|
|
262
|
+
runtime, jsi::PropNameID::forAscii(runtime, "getResourceStats"), 0,
|
|
263
|
+
[](jsi::Runtime& rt, const jsi::Value&, const jsi::Value*, size_t) {
|
|
264
|
+
auto result = jsi::Object(rt);
|
|
265
|
+
auto add = [&](const char* name, const ResourceCounter& counter) {
|
|
266
|
+
result.setProperty(rt, (std::string(name) + "Current").c_str(),
|
|
267
|
+
static_cast<double>(counter.current.load()));
|
|
268
|
+
result.setProperty(rt, (std::string(name) + "Peak").c_str(),
|
|
269
|
+
static_cast<double>(counter.peak.load()));
|
|
270
|
+
};
|
|
271
|
+
add("frameBuffers", NativeResourceStats::frameBuffers);
|
|
272
|
+
add("frameBytes", NativeResourceStats::frameBytes);
|
|
273
|
+
add("decoderBuffers", NativeResourceStats::decoderBuffers);
|
|
274
|
+
add("decoderBytes", NativeResourceStats::decoderBytes);
|
|
275
|
+
add("decoders", NativeResourceStats::decoders);
|
|
276
|
+
add("encoders", NativeResourceStats::encoders);
|
|
277
|
+
add("encoderSubmittingBuffers", NativeResourceStats::encoderSubmittingBuffers);
|
|
278
|
+
add("encoderSubmittingBytes", NativeResourceStats::encoderSubmittingBytes);
|
|
279
|
+
return jsi::Value(std::move(result));
|
|
280
|
+
}));
|
|
281
|
+
RNSVModule.setProperty(runtime, "getBackendInfo",
|
|
282
|
+
jsi::Function::createFromHostFunction(
|
|
283
|
+
runtime, jsi::PropNameID::forAscii(runtime, "getBackendInfo"), 0,
|
|
284
|
+
[](jsi::Runtime& rt, const jsi::Value&, const jsi::Value*, size_t) {
|
|
285
|
+
auto result = jsi::Object(rt);
|
|
286
|
+
result.setProperty(rt, "platform", "ios");
|
|
287
|
+
result.setProperty(rt, "frameTransport", "cvpixelbuffer-bgra");
|
|
288
|
+
result.setProperty(rt, "preview", "webgpu-native-blit+gpu-owned-snapshot");
|
|
289
|
+
result.setProperty(rt, "gpuUploadScratchCapacityPerRuntime", 1);
|
|
290
|
+
// The export probes the GPU path at setup and warns when it falls back.
|
|
291
|
+
result.setProperty(rt, "export", "gpu-iosurface-bounded-pixelbuffer-pool");
|
|
292
|
+
result.setProperty(rt, "exportFallback", "cpu-readback-bounded-pixelbuffer-pool");
|
|
293
|
+
// Decoded frames are sampled through their IOSurface during a GPU
|
|
294
|
+
// export, copied into owned snapshots only if that import fails.
|
|
295
|
+
result.setProperty(rt, "exportSource", "iosurface-in-place");
|
|
296
|
+
result.setProperty(rt, "nativeFrameCapacity", 2);
|
|
297
|
+
result.setProperty(rt, "encoderPoolCapacity", 3);
|
|
298
|
+
result.setProperty(rt, "gpuDirectExport", true);
|
|
299
|
+
return jsi::Value(std::move(result));
|
|
300
|
+
}));
|
|
301
|
+
runtime.global().setProperty(runtime, "RNSkiaVideo", RNSVModule);
|
|
302
|
+
return @true;
|
|
303
|
+
}
|
|
304
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
305
|
+
(const facebook::react::ObjCTurboModule::InitParams&)params {
|
|
306
|
+
return std::make_shared<facebook::react::NativeReactNativeSkiaVideoSpecJSI>(
|
|
307
|
+
params);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@end
|
package/ios/SeekPolicy.h
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
namespace RNSkiaVideo {
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* How far ahead a seek is served by reading the reader on rather than by
|
|
7
|
+
* building a new one.
|
|
8
|
+
*
|
|
9
|
+
* A drag lands a seek every few frames; measured on a 4K HEVC clip, creating
|
|
10
|
+
* an AVAssetReader costs more than decoding the second of video it skips.
|
|
11
|
+
*/
|
|
12
|
+
constexpr double kSeekReadOnSeconds = 1.0;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether a seek to `target` can be served by the reader already running at
|
|
16
|
+
* `last`. Backwards never can: an AVAssetReader only moves forward.
|
|
17
|
+
*/
|
|
18
|
+
inline bool seekReadsOn(bool readerRunning, bool lastValid, double last,
|
|
19
|
+
double target) {
|
|
20
|
+
if (!readerRunning || !lastValid) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
double ahead = target - last;
|
|
24
|
+
return ahead >= 0 && ahead <= kSeekReadOnSeconds;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import <CoreGraphics/CoreGraphics.h>
|
|
4
|
+
|
|
5
|
+
#include <jsi/jsi.h>
|
|
6
|
+
#include "RNSVCheckedSizes.h"
|
|
7
|
+
#include <set>
|
|
8
|
+
|
|
9
|
+
namespace RNSkiaVideo {
|
|
10
|
+
using namespace facebook;
|
|
11
|
+
|
|
12
|
+
class VideoCompositionItem {
|
|
13
|
+
public:
|
|
14
|
+
std::string id;
|
|
15
|
+
std::string path;
|
|
16
|
+
double compositionStartTime;
|
|
17
|
+
double startTime;
|
|
18
|
+
double duration;
|
|
19
|
+
CGSize resolution;
|
|
20
|
+
/**
|
|
21
|
+
* Cap for the longest side of the decoded frames, or 0 for the file's own
|
|
22
|
+
* size. Applied here rather than by the caller because only the decoder knows
|
|
23
|
+
* the track's encoded orientation: a portrait clip is a landscape frame plus a
|
|
24
|
+
* rotation, so a caller passing a portrait size for one gets a squashed
|
|
25
|
+
* picture. See VideoCompositionItemDecoder#setupReader.
|
|
26
|
+
*/
|
|
27
|
+
double maxLongSide = 0;
|
|
28
|
+
/**
|
|
29
|
+
* `textureMode: 'direct'` hands Skia the decoder's own pixel buffers,
|
|
30
|
+
* without the per-frame copy into a persistent texture that `'copy'` (the
|
|
31
|
+
* default) does. iOS only.
|
|
32
|
+
*/
|
|
33
|
+
bool directTexture = false;
|
|
34
|
+
bool isVideo = true;
|
|
35
|
+
bool audioEnabled = false;
|
|
36
|
+
double audioVolume = 1.0;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
class VideoComposition {
|
|
40
|
+
public:
|
|
41
|
+
double duration;
|
|
42
|
+
std::vector<std::shared_ptr<VideoCompositionItem>> items;
|
|
43
|
+
/** Decoders opened around their item's time, see DecoderWindow. */
|
|
44
|
+
bool lazyDecoders = false;
|
|
45
|
+
|
|
46
|
+
bool hasAudio() const {
|
|
47
|
+
for (const auto& item : items) {
|
|
48
|
+
if (item->audioEnabled) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static std::shared_ptr<VideoComposition> fromJS(jsi::Runtime& runtime,
|
|
56
|
+
jsi::Object& jsComposition) {
|
|
57
|
+
auto composition = std::make_shared<VideoComposition>();
|
|
58
|
+
composition->duration =
|
|
59
|
+
checkedMediaSeconds(jsComposition.getProperty(runtime, "duration").asNumber());
|
|
60
|
+
if (jsComposition.hasProperty(runtime, "lazyDecoders")) {
|
|
61
|
+
auto lazyProp = jsComposition.getProperty(runtime, "lazyDecoders");
|
|
62
|
+
composition->lazyDecoders = lazyProp.isBool() && lazyProp.getBool();
|
|
63
|
+
}
|
|
64
|
+
auto jsItems = jsComposition.getProperty(runtime, "items")
|
|
65
|
+
.asObject(runtime)
|
|
66
|
+
.asArray(runtime);
|
|
67
|
+
auto size = jsItems.size(runtime);
|
|
68
|
+
std::set<std::string> ids;
|
|
69
|
+
for (size_t i = 0; i < size; i++) {
|
|
70
|
+
auto jsItem = jsItems.getValueAtIndex(runtime, i).asObject(runtime);
|
|
71
|
+
auto item = std::make_shared<VideoCompositionItem>();
|
|
72
|
+
item->id =
|
|
73
|
+
jsItem.getProperty(runtime, "id").asString(runtime).utf8(runtime);
|
|
74
|
+
if (item->id.empty() || !ids.insert(item->id).second)
|
|
75
|
+
throw jsi::JSError(runtime, "Composition item IDs must be nonempty and unique");
|
|
76
|
+
item->path =
|
|
77
|
+
jsItem.getProperty(runtime, "path").asString(runtime).utf8(runtime);
|
|
78
|
+
item->compositionStartTime =
|
|
79
|
+
checkedMediaSeconds(jsItem.getProperty(runtime, "compositionStartTime").asNumber());
|
|
80
|
+
item->startTime = checkedMediaSeconds(jsItem.getProperty(runtime, "startTime").asNumber());
|
|
81
|
+
item->duration = checkedMediaSeconds(jsItem.getProperty(runtime, "duration").asNumber());
|
|
82
|
+
checkedMediaSeconds(item->startTime + item->duration);
|
|
83
|
+
checkedMediaSeconds(item->compositionStartTime + item->duration);
|
|
84
|
+
item->resolution = CGSize();
|
|
85
|
+
if (jsItem.hasProperty(runtime, "resolution")) {
|
|
86
|
+
auto resProp = jsItem.getProperty(runtime, "resolution");
|
|
87
|
+
if (resProp.isObject()) {
|
|
88
|
+
auto res = resProp.asObject(runtime);
|
|
89
|
+
item->resolution.width = checkedPositiveInt(res.getProperty(runtime, "width").asNumber(), "decode width");
|
|
90
|
+
item->resolution.height =
|
|
91
|
+
checkedPositiveInt(res.getProperty(runtime, "height").asNumber(), "decode height");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (jsItem.hasProperty(runtime, "maxLongSide")) {
|
|
96
|
+
auto capProp = jsItem.getProperty(runtime, "maxLongSide");
|
|
97
|
+
if (capProp.isNumber()) {
|
|
98
|
+
item->maxLongSide = checkedStorageInteger(capProp.asNumber(), "decode maxLongSide");
|
|
99
|
+
if (item->maxLongSide > std::numeric_limits<int>::max())
|
|
100
|
+
throw jsi::JSError(runtime, "Invalid decode maxLongSide");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (jsItem.hasProperty(runtime, "textureMode")) {
|
|
105
|
+
auto modeProp = jsItem.getProperty(runtime, "textureMode");
|
|
106
|
+
if (modeProp.isString()) {
|
|
107
|
+
item->directTexture =
|
|
108
|
+
modeProp.asString(runtime).utf8(runtime) == "direct";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (jsItem.hasProperty(runtime, "kind")) {
|
|
113
|
+
auto kindProp = jsItem.getProperty(runtime, "kind");
|
|
114
|
+
if (kindProp.isString()) {
|
|
115
|
+
item->isVideo = kindProp.asString(runtime).utf8(runtime) != "audio";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!item->isVideo) {
|
|
119
|
+
item->audioEnabled = true;
|
|
120
|
+
if (jsItem.hasProperty(runtime, "volume")) {
|
|
121
|
+
auto volumeProp = jsItem.getProperty(runtime, "volume");
|
|
122
|
+
if (volumeProp.isNumber()) {
|
|
123
|
+
item->audioVolume = volumeProp.asNumber();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else if (jsItem.hasProperty(runtime, "audio")) {
|
|
127
|
+
auto audioProp = jsItem.getProperty(runtime, "audio");
|
|
128
|
+
if (audioProp.isBool()) {
|
|
129
|
+
item->audioEnabled = audioProp.getBool();
|
|
130
|
+
} else if (audioProp.isObject()) {
|
|
131
|
+
item->audioEnabled = true;
|
|
132
|
+
auto audio = audioProp.asObject(runtime);
|
|
133
|
+
if (audio.hasProperty(runtime, "volume")) {
|
|
134
|
+
auto volumeProp = audio.getProperty(runtime, "volume");
|
|
135
|
+
if (volumeProp.isNumber()) {
|
|
136
|
+
item->audioVolume = volumeProp.asNumber();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
composition->items.push_back(item);
|
|
143
|
+
}
|
|
144
|
+
return composition;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
} // namespace RNSkiaVideo
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import "DecoderWindow.h"
|
|
4
|
+
#import "RNSVEventEmitter.h"
|
|
5
|
+
#import "RNSVHostObject.h"
|
|
6
|
+
#import "VideoComposition.h"
|
|
7
|
+
#import "VideoCompositionItemDecoder.h"
|
|
8
|
+
#import "VideoFrame.h"
|
|
9
|
+
#import <AVFoundation/AVFoundation.h>
|
|
10
|
+
#import <QuartzCore/QuartzCore.h>
|
|
11
|
+
#import <atomic>
|
|
12
|
+
#import <jsi/jsi.h>
|
|
13
|
+
#import <map>
|
|
14
|
+
#import <set>
|
|
15
|
+
#import <vector>
|
|
16
|
+
|
|
17
|
+
@interface RNSVDisplayLinkWrapper : NSObject
|
|
18
|
+
|
|
19
|
+
@property(nonatomic, strong) CADisplayLink* displayLink;
|
|
20
|
+
@property(nonatomic, copy) void (^updateBlock)(CADisplayLink* displayLink);
|
|
21
|
+
|
|
22
|
+
- (instancetype)initWithUpdateBlock:
|
|
23
|
+
(void (^)(CADisplayLink* displayLink))updateBlock;
|
|
24
|
+
- (void)start;
|
|
25
|
+
- (void)invalidate;
|
|
26
|
+
|
|
27
|
+
@end
|
|
28
|
+
|
|
29
|
+
namespace RNSkiaVideo {
|
|
30
|
+
using namespace facebook;
|
|
31
|
+
|
|
32
|
+
class JSI_EXPORT VideoCompositionFramesExtractorHostObject
|
|
33
|
+
: public RNSVHostObject,
|
|
34
|
+
EventEmitter {
|
|
35
|
+
public:
|
|
36
|
+
VideoCompositionFramesExtractorHostObject(
|
|
37
|
+
jsi::Runtime& runtime, std::shared_ptr<react::CallInvoker> callInvoker,
|
|
38
|
+
std::shared_ptr<VideoComposition> videoComposition);
|
|
39
|
+
~VideoCompositionFramesExtractorHostObject();
|
|
40
|
+
jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override;
|
|
41
|
+
void set(jsi::Runtime&, const jsi::PropNameID& name,
|
|
42
|
+
const jsi::Value& value) override;
|
|
43
|
+
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;
|
|
44
|
+
|
|
45
|
+
private:
|
|
46
|
+
NSObject* lock;
|
|
47
|
+
std::shared_ptr<VideoComposition> composition;
|
|
48
|
+
DecoderWindow window;
|
|
49
|
+
std::map<std::string, std::shared_ptr<VideoCompositionItemDecoder>>
|
|
50
|
+
itemDecoders;
|
|
51
|
+
// Items whose decoder could not be opened lazily: not tried again.
|
|
52
|
+
std::set<std::string> failedItems;
|
|
53
|
+
// Shared by the decoders and the audio; decoder queue only.
|
|
54
|
+
NSMutableDictionary<NSString*, AVURLAsset*>* assetCache;
|
|
55
|
+
// Tells whether a seek happened while a decoder was being opened.
|
|
56
|
+
uint64_t seekGeneration = 0;
|
|
57
|
+
// What the decoders were last advanced to, so a paused player stops
|
|
58
|
+
// asking them again at every vsync.
|
|
59
|
+
CMTime lastAdvancedTime = kCMTimeInvalid;
|
|
60
|
+
uint64_t lastAdvancedGeneration = 0;
|
|
61
|
+
// Seeks and reader advancement share the serial decoder queue. UI calls
|
|
62
|
+
// update the desired clock; they never replace a reader being decoded.
|
|
63
|
+
uint64_t lastAppliedSeekGeneration = 0;
|
|
64
|
+
std::map<std::string, std::shared_ptr<VideoFrame>> currentFrames;
|
|
65
|
+
// Incremented every time decodeCompositionFrames acquires a new frame for
|
|
66
|
+
// at least one item. Exposed to JS as `framesVersion`, so the player can
|
|
67
|
+
// tell an unchanged frame set from a fresh one and skip redrawing it.
|
|
68
|
+
uint64_t framesVersion = 0;
|
|
69
|
+
dispatch_queue_t decoderQueue;
|
|
70
|
+
std::atomic<bool> decoderTickPending{false};
|
|
71
|
+
RNSVDisplayLinkWrapper* displayLink;
|
|
72
|
+
NSDate* startDate;
|
|
73
|
+
CMTime pausePosition = kCMTimeZero;
|
|
74
|
+
bool playWhenReady = false;
|
|
75
|
+
bool isPlaying = false;
|
|
76
|
+
bool isLooping = false;
|
|
77
|
+
bool initialized = false;
|
|
78
|
+
/** Set by a seek, cleared by the first frame drawn after it. */
|
|
79
|
+
std::atomic<bool> seekPending = false;
|
|
80
|
+
bool completeEmitted = false;
|
|
81
|
+
// Plays the audio of the composition; when present it is also the master
|
|
82
|
+
// clock of the playback.
|
|
83
|
+
AVPlayer* audioPlayer;
|
|
84
|
+
std::atomic_flag released = ATOMIC_FLAG_INIT;
|
|
85
|
+
|
|
86
|
+
void prepare();
|
|
87
|
+
void scheduleDecode();
|
|
88
|
+
void decodeTick();
|
|
89
|
+
void init();
|
|
90
|
+
void play();
|
|
91
|
+
void pause();
|
|
92
|
+
void seekTo(CMTime time);
|
|
93
|
+
CMTime getCurrentTime();
|
|
94
|
+
void release();
|
|
95
|
+
bool opensAt(const std::shared_ptr<VideoCompositionItem>& item,
|
|
96
|
+
double position) const;
|
|
97
|
+
void updateWindow(
|
|
98
|
+
CMTime time, std::vector<std::shared_ptr<VideoCompositionItem>>& opening,
|
|
99
|
+
std::vector<std::shared_ptr<VideoCompositionItemDecoder>>& closing);
|
|
100
|
+
void
|
|
101
|
+
openDecoders(const std::vector<std::shared_ptr<VideoCompositionItem>>& items,
|
|
102
|
+
CMTime time, uint64_t generation);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
} // namespace RNSkiaVideo
|