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.
- 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/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 +35 -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 +83 -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 +772 -0
- package/android/src/main/java/com/azzapp/rnskv/VideoCompositionFramesExtractor.java +354 -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 +348 -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 +85 -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 +154 -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 +520 -0
- package/src/videoFrameImage.ts +205 -0
- package/src/videoPlayer.ts +318 -0
- package/src/yuvBlit.ts +331 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
import type { SkCanvas } from "react-native-skia";
|
|
2
|
+
|
|
3
|
+
/** A transient frame. Use makeVideoFrameImage/drawVideoFrame to obtain owned
|
|
4
|
+
* Skia pixels; raw Metal/GL handles are no longer supported by Graphite. */
|
|
5
|
+
export type VideoFrame = {
|
|
6
|
+
texture: unknown;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
rotation: number;
|
|
10
|
+
crop?: { x: number; y: number; width: number; height: number };
|
|
11
|
+
/** Stable identity of the native frame, including after its buffer is released. */
|
|
12
|
+
id?: number;
|
|
13
|
+
/** Stable identity of the producer, used to bound imported image caches. */
|
|
14
|
+
producerId?: number;
|
|
15
|
+
/** Release a native source after copying/rendering and waiting for GPU completion. */
|
|
16
|
+
dispose?: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Represents the dimensions of a video.
|
|
21
|
+
*/
|
|
22
|
+
export type VideoDimensions = {
|
|
23
|
+
/**
|
|
24
|
+
* The width in pixels of the video.
|
|
25
|
+
*/
|
|
26
|
+
width: number;
|
|
27
|
+
/**
|
|
28
|
+
* The height in pixels of the video.
|
|
29
|
+
*/
|
|
30
|
+
height: number;
|
|
31
|
+
/**
|
|
32
|
+
* The rotation in degrees of the video.
|
|
33
|
+
*/
|
|
34
|
+
rotation: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Represents a range of buffered data.
|
|
39
|
+
*/
|
|
40
|
+
export type BufferingRange = { start: number; duration: number };
|
|
41
|
+
|
|
42
|
+
/** Compatibility preference retained from Skia Video. This reference
|
|
43
|
+
* Graphite backend uses bounded, owned snapshots for both values. Native
|
|
44
|
+
* buffers first pass through a reusable WebGPU upload texture. Neither
|
|
45
|
+
* value exposes the legacy Metal/OpenGL texture lifetime contract. */
|
|
46
|
+
export type VideoTextureMode = "copy" | "direct";
|
|
47
|
+
|
|
48
|
+
/** Compatibility preference; both values use the same transport. iOS draws
|
|
49
|
+
* into the encoder's pool buffers on the GPU, falling back to CPU readback
|
|
50
|
+
* when IOSurface interop is unavailable. Android reads pixels back. */
|
|
51
|
+
export type VideoEncoderMode = "copy" | "direct";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The video player interface.
|
|
55
|
+
*/
|
|
56
|
+
export type VideoPlayer = {
|
|
57
|
+
/**
|
|
58
|
+
* Resumes playback of the video.
|
|
59
|
+
*/
|
|
60
|
+
play(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Pauses playback.
|
|
63
|
+
*/
|
|
64
|
+
pause(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Seeks to a position specified in seconds.
|
|
67
|
+
* Once the seek is complete, the `seekComplete` event will be emitted.
|
|
68
|
+
*
|
|
69
|
+
* @param time The position in seconds to seek to.
|
|
70
|
+
*/
|
|
71
|
+
seekTo(time: number): void;
|
|
72
|
+
/**
|
|
73
|
+
* Decodes the next frame of the video.
|
|
74
|
+
* This method should only be called from the ui thread.
|
|
75
|
+
*
|
|
76
|
+
* @returns The next frame of the video.
|
|
77
|
+
*/
|
|
78
|
+
decodeNextFrame(): VideoFrame | null;
|
|
79
|
+
/**
|
|
80
|
+
* The current time in seconds of the playback.
|
|
81
|
+
*/
|
|
82
|
+
readonly currentTime: number;
|
|
83
|
+
/**
|
|
84
|
+
* The duration in seconds of the video.
|
|
85
|
+
*/
|
|
86
|
+
readonly duration: number;
|
|
87
|
+
/**
|
|
88
|
+
* Indicates whether the video is currently playing.
|
|
89
|
+
*/
|
|
90
|
+
readonly isPlaying: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Indicates whether the video is set to loop.
|
|
93
|
+
*/
|
|
94
|
+
isLooping: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* The volume of the video.
|
|
97
|
+
* The value should be between 0 and 1.
|
|
98
|
+
*/
|
|
99
|
+
volume: number;
|
|
100
|
+
/**
|
|
101
|
+
* The playback speed of the video.
|
|
102
|
+
* The value should be greater than 0. 1.0 is normal speed, 2.0 is double speed, 0.5 is half speed.
|
|
103
|
+
*/
|
|
104
|
+
playbackSpeed: number;
|
|
105
|
+
/**
|
|
106
|
+
* Disposes of the video player.
|
|
107
|
+
*/
|
|
108
|
+
dispose(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Events dispatched by the video player once the video is ready to play.
|
|
111
|
+
*/
|
|
112
|
+
on(
|
|
113
|
+
name: "ready",
|
|
114
|
+
listener: (dimensions: VideoDimensions) => void,
|
|
115
|
+
): () => void;
|
|
116
|
+
/**
|
|
117
|
+
* Events dispatched by the video player when the video starts buffering.
|
|
118
|
+
*/
|
|
119
|
+
on(name: "bufferingStart", listener: () => void): () => void;
|
|
120
|
+
/**
|
|
121
|
+
* Events dispatched by the video player when the video stops buffering.
|
|
122
|
+
*/
|
|
123
|
+
on(name: "bufferingEnd", listener: () => void): () => void;
|
|
124
|
+
/**
|
|
125
|
+
* Events dispatched by the video player when the buffered ranges are updated.
|
|
126
|
+
*/
|
|
127
|
+
on(
|
|
128
|
+
name: "bufferingUpdate",
|
|
129
|
+
listener: (loadedRanges: BufferingRange[]) => void,
|
|
130
|
+
): () => void;
|
|
131
|
+
/**
|
|
132
|
+
* Events dispatched by the video player when the video playback completes.
|
|
133
|
+
*/
|
|
134
|
+
on(name: "complete", listener: () => void): () => void;
|
|
135
|
+
/**
|
|
136
|
+
* Events dispatched by the video player when a seek operation completes.
|
|
137
|
+
*/
|
|
138
|
+
on(name: "seekComplete", listener: () => void): () => void;
|
|
139
|
+
/**
|
|
140
|
+
* Events dispatched by the video player when the playing status changes.
|
|
141
|
+
*/
|
|
142
|
+
on(
|
|
143
|
+
name: "playingStatusChange",
|
|
144
|
+
listener: (isPlaying: boolean) => void,
|
|
145
|
+
): () => void;
|
|
146
|
+
/**
|
|
147
|
+
* Events dispatched by the video player when an error occurs.
|
|
148
|
+
*/
|
|
149
|
+
on(name: "error", listener: (error: any) => void): () => void;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Represents a video composition.
|
|
154
|
+
*/
|
|
155
|
+
export type VideoComposition = {
|
|
156
|
+
/**
|
|
157
|
+
* The items that make up the composition.
|
|
158
|
+
*/
|
|
159
|
+
items: VideoCompositionItem[];
|
|
160
|
+
/**
|
|
161
|
+
* The duration in seconds of the composition.
|
|
162
|
+
*/
|
|
163
|
+
duration: number;
|
|
164
|
+
/**
|
|
165
|
+
* Open each video item's decoder shortly before the item starts and close it
|
|
166
|
+
* shortly after it ends, instead of all of them at prepare. For compositions
|
|
167
|
+
* that play their items one after another: decoders and their frames are
|
|
168
|
+
* held for a few items at a time rather than for all of them.
|
|
169
|
+
*
|
|
170
|
+
* An item reached by a seek, rather than by playing up to it, shows no frame
|
|
171
|
+
* until its decoder has opened. The export always waits for it.
|
|
172
|
+
*/
|
|
173
|
+
lazyDecoders?: boolean;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
type VideoCompositionItemBase = {
|
|
177
|
+
/**
|
|
178
|
+
* The unique identifier of the item.
|
|
179
|
+
*/
|
|
180
|
+
id: string;
|
|
181
|
+
/**
|
|
182
|
+
* The path to the media file.
|
|
183
|
+
* only support local file path
|
|
184
|
+
*/
|
|
185
|
+
path: string;
|
|
186
|
+
/**
|
|
187
|
+
* The start time in seconds of the item within the composition.
|
|
188
|
+
*/
|
|
189
|
+
compositionStartTime: number;
|
|
190
|
+
/**
|
|
191
|
+
* The start time in seconds of the item within the media.
|
|
192
|
+
*/
|
|
193
|
+
startTime: number;
|
|
194
|
+
/**
|
|
195
|
+
* The duration in seconds of the item.
|
|
196
|
+
*/
|
|
197
|
+
duration: number;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* A video item of a composition. Produces frames passed to the `drawFrame`
|
|
202
|
+
* function. Video items are silent by default; set `audio` to also play
|
|
203
|
+
* the audio track of the video file.
|
|
204
|
+
*/
|
|
205
|
+
export type VideoCompositionVideoItem = VideoCompositionItemBase & {
|
|
206
|
+
kind?: "video";
|
|
207
|
+
/**
|
|
208
|
+
* If provided, the resolution to scale the video to.
|
|
209
|
+
* If not provided, the original resolution of the video will be used.
|
|
210
|
+
* Downscaling the video can improve performance.
|
|
211
|
+
*
|
|
212
|
+
* Given in the file's *encoded* orientation, which for a portrait clip is
|
|
213
|
+
* the transpose of what it displays as — prefer `maxLongSide` unless an
|
|
214
|
+
* exact pixel size is needed.
|
|
215
|
+
*/
|
|
216
|
+
resolution?: { width: number; height: number };
|
|
217
|
+
/**
|
|
218
|
+
* If provided, the longest side the frames are decoded at, keeping the
|
|
219
|
+
* file's own aspect. Ignored when `resolution` is set, and never upscales.
|
|
220
|
+
*
|
|
221
|
+
* The cap reduces decoder output/staging pixels. Android's hardware
|
|
222
|
+
* decoder can still allocate buffers at the encoded resolution; measure
|
|
223
|
+
* process/GPU memory separately from the library's explicit budget.
|
|
224
|
+
*/
|
|
225
|
+
maxLongSide?: number;
|
|
226
|
+
/**
|
|
227
|
+
* How this item's frames reach Skia, see {@link VideoTextureMode}.
|
|
228
|
+
*
|
|
229
|
+
* @default 'copy'
|
|
230
|
+
*/
|
|
231
|
+
textureMode?: VideoTextureMode;
|
|
232
|
+
/**
|
|
233
|
+
* If set, the audio track of the video file will be played (during
|
|
234
|
+
* playback) and mixed into the exported video (during export), following
|
|
235
|
+
* the same time mapping as the video frames.
|
|
236
|
+
* Defaults to false (video items are silent).
|
|
237
|
+
*/
|
|
238
|
+
audio?: boolean | { volume?: number };
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* An audio item of a composition. Does not produce frames; its audio track
|
|
243
|
+
* is played during playback and mixed into the exported video during export.
|
|
244
|
+
* The source file can be an audio file or a video file (in which case its
|
|
245
|
+
* audio track is used).
|
|
246
|
+
*/
|
|
247
|
+
export type VideoCompositionAudioItem = VideoCompositionItemBase & {
|
|
248
|
+
kind: "audio";
|
|
249
|
+
/**
|
|
250
|
+
* The volume of the item, between 0 and 1.
|
|
251
|
+
* Defaults to 1.
|
|
252
|
+
*/
|
|
253
|
+
volume?: number;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export type VideoCompositionItem =
|
|
257
|
+
VideoCompositionVideoItem | VideoCompositionAudioItem;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Function that draws a video composition frame to a canvas.
|
|
261
|
+
*/
|
|
262
|
+
export type FrameDrawer<T = undefined> = (args: {
|
|
263
|
+
/**
|
|
264
|
+
* The context created by the `before` function in video composition player.
|
|
265
|
+
* or in export video composition.
|
|
266
|
+
*/
|
|
267
|
+
context: T;
|
|
268
|
+
/**
|
|
269
|
+
* The canvas to draw the frame to.
|
|
270
|
+
*/
|
|
271
|
+
canvas: SkCanvas;
|
|
272
|
+
/**
|
|
273
|
+
* The current composition
|
|
274
|
+
*/
|
|
275
|
+
videoComposition: VideoComposition;
|
|
276
|
+
/**
|
|
277
|
+
* The current time in seconds of the drawn frame.
|
|
278
|
+
*/
|
|
279
|
+
currentTime: number;
|
|
280
|
+
/**
|
|
281
|
+
* The decoded video frames of the composition items.
|
|
282
|
+
* Only video items produce frames; audio items never appear in this map.
|
|
283
|
+
*/
|
|
284
|
+
frames: Record<string, VideoFrame>;
|
|
285
|
+
/**
|
|
286
|
+
* The expected width of the frame in pixels.
|
|
287
|
+
*/
|
|
288
|
+
width: number;
|
|
289
|
+
/**
|
|
290
|
+
* The expected height of the frame in pixels.
|
|
291
|
+
*/
|
|
292
|
+
height: number;
|
|
293
|
+
}) => void;
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* The video composition frames extractor interface.
|
|
297
|
+
*/
|
|
298
|
+
export type VideoCompositionFramesExtractor = {
|
|
299
|
+
/**
|
|
300
|
+
* Prepares the video composition frames extractor for extracting the frames.
|
|
301
|
+
*/
|
|
302
|
+
prepare(): void;
|
|
303
|
+
/**
|
|
304
|
+
* Starts extracting the frames of the video composition.
|
|
305
|
+
*/
|
|
306
|
+
play(): void;
|
|
307
|
+
/**
|
|
308
|
+
* Pauses the extraction of the frames.
|
|
309
|
+
*/
|
|
310
|
+
pause(): void;
|
|
311
|
+
/**
|
|
312
|
+
* Seeks to a position specified in seconds.
|
|
313
|
+
* @param time The position in seconds to seek to.
|
|
314
|
+
*/
|
|
315
|
+
seekTo(time: number): void;
|
|
316
|
+
/**
|
|
317
|
+
* Decodes the frames of the video composition items.
|
|
318
|
+
* This method should only be called from the ui thread.
|
|
319
|
+
*
|
|
320
|
+
* @returns The decoded video frames of the composition items.
|
|
321
|
+
*/
|
|
322
|
+
decodeCompositionFrames(): Record<string, VideoFrame>;
|
|
323
|
+
/**
|
|
324
|
+
* Disposes of the video composition frames extractor.
|
|
325
|
+
*/
|
|
326
|
+
dispose(): void;
|
|
327
|
+
/**
|
|
328
|
+
* The current time in seconds.
|
|
329
|
+
*/
|
|
330
|
+
readonly currentTime: number;
|
|
331
|
+
/**
|
|
332
|
+
* A counter incremented every time `decodeCompositionFrames` returned a new
|
|
333
|
+
* frame for at least one item. Two equal values mean the frames did not
|
|
334
|
+
* change in between.
|
|
335
|
+
*/
|
|
336
|
+
readonly framesVersion: number;
|
|
337
|
+
/**
|
|
338
|
+
* Whether the video composition frames extractor is currently playing.
|
|
339
|
+
*/
|
|
340
|
+
readonly isPlaying: boolean;
|
|
341
|
+
/**
|
|
342
|
+
* Whether the video composition frames extractor is set to loop.
|
|
343
|
+
*/
|
|
344
|
+
isLooping: boolean;
|
|
345
|
+
/**
|
|
346
|
+
* Events dispatched by the video composition frames extractor when the extraction is ready.
|
|
347
|
+
*/
|
|
348
|
+
on(name: "ready", listener: () => void): () => void;
|
|
349
|
+
/**
|
|
350
|
+
* Events dispatched by the video composition frames extractor process completes.
|
|
351
|
+
*/
|
|
352
|
+
on(name: "complete", listener: () => void): () => void;
|
|
353
|
+
/**
|
|
354
|
+
* Events dispatched by the video composition frames extractor when an error occurs.
|
|
355
|
+
*/
|
|
356
|
+
on(name: "error", listener: (error: any) => void): () => void;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* The video composition sync extractor interface.
|
|
361
|
+
*/
|
|
362
|
+
export type VideoCompositionFramesExtractorSync = {
|
|
363
|
+
/**
|
|
364
|
+
* Starts extracting the frames of the video composition.
|
|
365
|
+
*/
|
|
366
|
+
start(): void;
|
|
367
|
+
/**
|
|
368
|
+
* Decodes the frames until reaching the specified time.
|
|
369
|
+
* This method will block the current thread until the frames are decoded.
|
|
370
|
+
*
|
|
371
|
+
* The returned frames are only valid until the next call: draw them and
|
|
372
|
+
* flush the GPU synchronously (`surface.flush(true)`) before decoding
|
|
373
|
+
* further frames.
|
|
374
|
+
*
|
|
375
|
+
* @returns The decoded video frames of the composition items.
|
|
376
|
+
*/
|
|
377
|
+
decodeCompositionFrames(currentTime: number): Record<string, VideoFrame>;
|
|
378
|
+
/**
|
|
379
|
+
* Disposes of the video composition frames extractor.
|
|
380
|
+
*/
|
|
381
|
+
dispose(): void;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* The video composition encoder interface.
|
|
386
|
+
*/
|
|
387
|
+
export type VideoEncoder = {
|
|
388
|
+
/**
|
|
389
|
+
* Prepares the video composition encoder for writing.
|
|
390
|
+
*/
|
|
391
|
+
prepare(): void;
|
|
392
|
+
/**
|
|
393
|
+
* Encodes the video frame to the video composition.
|
|
394
|
+
*/
|
|
395
|
+
encodeFrame(texture: unknown, time: number): void;
|
|
396
|
+
/**
|
|
397
|
+
* Lends the next pool buffer to draw into on the GPU: a CVPixelBuffer on
|
|
398
|
+
* iOS, an RGBA AHardwareBuffer on Android. Pass it back as a native-buffer
|
|
399
|
+
* frame (on Android with the sync-fds ending its GPU access, in `fences`),
|
|
400
|
+
* or release it. One at a time.
|
|
401
|
+
*/
|
|
402
|
+
acquireFrameBuffer?(): bigint;
|
|
403
|
+
/** Returns a lent buffer that will not be encoded. */
|
|
404
|
+
releaseFrameBuffer?(): void;
|
|
405
|
+
/*
|
|
406
|
+
* Finish writing the video to the output file.
|
|
407
|
+
*/
|
|
408
|
+
finishWriting(): void;
|
|
409
|
+
/**
|
|
410
|
+
* Disposes of the video composition encoder.
|
|
411
|
+
*/
|
|
412
|
+
dispose(): void;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* The video codec used to encode an export.
|
|
417
|
+
*
|
|
418
|
+
* `h264` is supported by every device this library runs on, and is what
|
|
419
|
+
* playback and upload pipelines are universally built around. `hevc` produces
|
|
420
|
+
* roughly the same quality at about half the bitrate, but is not available
|
|
421
|
+
* everywhere — see {@link RNSkiaVideoModule.isEncodingSupported}.
|
|
422
|
+
*/
|
|
423
|
+
export type VideoCodec = "h264" | "hevc";
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* The export options for a video composition.
|
|
427
|
+
*/
|
|
428
|
+
export type ExportOptions = {
|
|
429
|
+
/**
|
|
430
|
+
* The path to save the exported video.
|
|
431
|
+
*/
|
|
432
|
+
outPath: string;
|
|
433
|
+
/**
|
|
434
|
+
* The width of the exported video in pixels.
|
|
435
|
+
*/
|
|
436
|
+
width: number;
|
|
437
|
+
/**
|
|
438
|
+
* The height of the exported video in pixels.
|
|
439
|
+
*/
|
|
440
|
+
height: number;
|
|
441
|
+
/**
|
|
442
|
+
* The frame rate of the exported video in frames per second.
|
|
443
|
+
*/
|
|
444
|
+
frameRate: number;
|
|
445
|
+
/**
|
|
446
|
+
* The bit rate of the exported video in bits per second.
|
|
447
|
+
*/
|
|
448
|
+
bitRate: number;
|
|
449
|
+
/**
|
|
450
|
+
* The video codec to encode with.
|
|
451
|
+
*
|
|
452
|
+
* Falls back to `h264` when the requested codec has no encoder on the
|
|
453
|
+
* device, so passing `hevc` can never fail an export that `h264` would have
|
|
454
|
+
* completed. Call {@link RNSkiaVideoModule.isEncodingSupported} first if the
|
|
455
|
+
* choice is surfaced to a user, so the option can be hidden rather than
|
|
456
|
+
* silently ignored.
|
|
457
|
+
*
|
|
458
|
+
* @default 'h264'
|
|
459
|
+
*/
|
|
460
|
+
codec?: VideoCodec;
|
|
461
|
+
/**
|
|
462
|
+
* The encoder name to use for the export.
|
|
463
|
+
*
|
|
464
|
+
* Takes precedence over {@link codec}: naming an encoder selects it
|
|
465
|
+
* directly, whatever codec it implements.
|
|
466
|
+
*
|
|
467
|
+
* @platform android
|
|
468
|
+
*/
|
|
469
|
+
encoderName?: string | null;
|
|
470
|
+
/**
|
|
471
|
+
* How rendered frames reach the encoder, see {@link VideoEncoderMode}.
|
|
472
|
+
*
|
|
473
|
+
* @default 'copy'
|
|
474
|
+
* @platform ios
|
|
475
|
+
*/
|
|
476
|
+
encoderMode?: VideoEncoderMode;
|
|
477
|
+
/**
|
|
478
|
+
* The bit rate of the exported audio track in bits per second.
|
|
479
|
+
* Only used if the composition contains audio.
|
|
480
|
+
* @default 128000
|
|
481
|
+
*/
|
|
482
|
+
audioBitRate?: number;
|
|
483
|
+
/**
|
|
484
|
+
* The sample rate of the exported audio track in Hz.
|
|
485
|
+
* Only used if the composition contains audio.
|
|
486
|
+
* @default 44100
|
|
487
|
+
*/
|
|
488
|
+
audioSampleRate?: number;
|
|
489
|
+
/**
|
|
490
|
+
* The number of channels of the exported audio track (1 = mono, 2 = stereo).
|
|
491
|
+
* Only used if the composition contains audio.
|
|
492
|
+
* @default 2
|
|
493
|
+
*/
|
|
494
|
+
audioChannelCount?: number;
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
export type RNSkiaVideoModule = {
|
|
498
|
+
frameProtocolVersion?: number;
|
|
499
|
+
/** Internal Android capability negotiation, before creating a decoder. */
|
|
500
|
+
configureNativeBufferInterop?: (enabled: boolean) => void;
|
|
501
|
+
reserveMemory?: (bytes: number, label: string) => number;
|
|
502
|
+
releaseMemory?: (reservation: number) => void;
|
|
503
|
+
configureMemoryBudget?: (maxBytes: number) => void;
|
|
504
|
+
getMemoryBudgetStats?: () => {
|
|
505
|
+
currentBytes: number;
|
|
506
|
+
peakBytes: number;
|
|
507
|
+
allocations: number;
|
|
508
|
+
maxBytes: number;
|
|
509
|
+
};
|
|
510
|
+
getResourceStats?: () => Record<string, number>;
|
|
511
|
+
getBackendInfo?: () => Record<string, string | number | boolean>;
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Creates a video player for the specified video file.
|
|
515
|
+
*
|
|
516
|
+
* @param uri The path to the video file.
|
|
517
|
+
* @param resolution If provided, the resolution to scale the video to.
|
|
518
|
+
* If not provided, the original resolution of the video will be used.
|
|
519
|
+
* Downscaling the video can improve performance.
|
|
520
|
+
* @returns The video player.
|
|
521
|
+
*/
|
|
522
|
+
createVideoPlayer: (
|
|
523
|
+
uri: string,
|
|
524
|
+
resolution?: { width: number; height: number } | null,
|
|
525
|
+
options?: { textureMode?: VideoTextureMode } | null,
|
|
526
|
+
) => VideoPlayer;
|
|
527
|
+
/**
|
|
528
|
+
* Creates a video composition frames extractor for the specified video composition.
|
|
529
|
+
* @param composition The video composition.
|
|
530
|
+
* @returns The video composition frames extractor.
|
|
531
|
+
*/
|
|
532
|
+
createVideoCompositionFramesExtractor: (
|
|
533
|
+
/**
|
|
534
|
+
* The video composition to extract frames from.
|
|
535
|
+
*/
|
|
536
|
+
composition: VideoComposition,
|
|
537
|
+
) => VideoCompositionFramesExtractor;
|
|
538
|
+
/**
|
|
539
|
+
* Creates a synchronous video composition frames extractor for the specified video composition.
|
|
540
|
+
* @param composition The video composition.
|
|
541
|
+
* @returns The video composition frames extractor.
|
|
542
|
+
*/
|
|
543
|
+
createVideoCompositionFramesExtractorSync: (
|
|
544
|
+
/**
|
|
545
|
+
* The video composition to extract frames from.
|
|
546
|
+
*/
|
|
547
|
+
composition: VideoComposition,
|
|
548
|
+
) => VideoCompositionFramesExtractorSync;
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Creates a video composition encoder for the specified export options.
|
|
552
|
+
* @param options The export options for the video composition.
|
|
553
|
+
* @param composition The video composition being exported; used to encode
|
|
554
|
+
* the audio tracks of the composition items (if any).
|
|
555
|
+
* @returns The video composition encoder.
|
|
556
|
+
*/
|
|
557
|
+
createVideoEncoder: (
|
|
558
|
+
/**
|
|
559
|
+
* The export options for the video composition.
|
|
560
|
+
*/
|
|
561
|
+
options: ExportOptions,
|
|
562
|
+
/**
|
|
563
|
+
* The video composition being exported (used for audio encoding).
|
|
564
|
+
*/
|
|
565
|
+
composition?: VideoComposition | null,
|
|
566
|
+
) => VideoEncoder;
|
|
567
|
+
/**
|
|
568
|
+
* Runs the given function inside an autorelease pool. Worklet runtime
|
|
569
|
+
* threads never drain their autorelease pool, so any per-frame native
|
|
570
|
+
* garbage created by code running there would otherwise accumulate for the
|
|
571
|
+
* lifetime of the app.
|
|
572
|
+
*
|
|
573
|
+
* @platform ios
|
|
574
|
+
*/
|
|
575
|
+
runWithAutoreleasePool?: <T>(fn: () => T) => T;
|
|
576
|
+
/**
|
|
577
|
+
* Returns the decoding capabilities of the current platform for the specified mimetype.
|
|
578
|
+
*
|
|
579
|
+
* @platform android
|
|
580
|
+
* @param mimetype The mimetype of the video.
|
|
581
|
+
*/
|
|
582
|
+
getDecodingCapabilitiesFor(mimetype: string): {
|
|
583
|
+
/**
|
|
584
|
+
* The maximum number of instances that can be decoded simultaneously.
|
|
585
|
+
*/
|
|
586
|
+
maxInstances: number;
|
|
587
|
+
/**
|
|
588
|
+
* The maximum width of the frame that the decoder will produce.
|
|
589
|
+
*/
|
|
590
|
+
maxWidth: number;
|
|
591
|
+
/**
|
|
592
|
+
* The maximum height of the frame that the decoder will produce.
|
|
593
|
+
*/
|
|
594
|
+
maxHeight: number;
|
|
595
|
+
} | null;
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Whether the device can encode with the given codec.
|
|
599
|
+
*
|
|
600
|
+
* `h264` is always true. `hevc` depends on the hardware: an A10 or later on
|
|
601
|
+
* iOS, and a device dependent answer on Android, where HEVC decoding is far
|
|
602
|
+
* more common than HEVC encoding.
|
|
603
|
+
*
|
|
604
|
+
* Exports fall back to `h264` on their own when this returns false, so this
|
|
605
|
+
* is for the UI — hiding or disabling a codec the device cannot honour is
|
|
606
|
+
* better than accepting the choice and quietly ignoring it.
|
|
607
|
+
*
|
|
608
|
+
* @param codec The codec to test.
|
|
609
|
+
*/
|
|
610
|
+
isEncodingSupported(codec: VideoCodec): boolean;
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Given a set of encoder configurations,
|
|
614
|
+
* returns the closest supported configurations by the platform encoders.
|
|
615
|
+
*
|
|
616
|
+
* @param width The width of the video.
|
|
617
|
+
* @param height The height of the video.
|
|
618
|
+
* @param frameRate The frame rate of the video in frames per second.
|
|
619
|
+
* @param bitRate The bit rate of the video in bits per second.
|
|
620
|
+
* @param codec The codec the export will encode with, defaulting to `h264`.
|
|
621
|
+
* Pass the same value here and to {@link ExportOptions.codec}: the sizes and
|
|
622
|
+
* frame rates an H.264 encoder accepts say nothing about what the HEVC one on
|
|
623
|
+
* the same chip will take. A codec the device cannot encode falls back to
|
|
624
|
+
* `h264`, as an export would.
|
|
625
|
+
*/
|
|
626
|
+
getValidEncoderConfigurations(
|
|
627
|
+
width: number,
|
|
628
|
+
height: number,
|
|
629
|
+
frameRate: number,
|
|
630
|
+
bitRate: number,
|
|
631
|
+
codec?: VideoCodec,
|
|
632
|
+
):
|
|
633
|
+
| {
|
|
634
|
+
/**
|
|
635
|
+
* The name of the encoder.
|
|
636
|
+
* can be reused in the `exportVideoComposition` method.
|
|
637
|
+
*/
|
|
638
|
+
encoderName: string;
|
|
639
|
+
/**
|
|
640
|
+
* Wether the encoder supports hardware acceleration.
|
|
641
|
+
*/
|
|
642
|
+
hardwareAccelerated: boolean;
|
|
643
|
+
/**
|
|
644
|
+
* The width of the video.
|
|
645
|
+
*/
|
|
646
|
+
width: number;
|
|
647
|
+
/**
|
|
648
|
+
* The height of the video.
|
|
649
|
+
*/
|
|
650
|
+
height: number;
|
|
651
|
+
/**
|
|
652
|
+
* The frame rate of the video in frames per second.
|
|
653
|
+
*/
|
|
654
|
+
frameRate: number;
|
|
655
|
+
/**
|
|
656
|
+
* The bit rate of the video in bits per second.
|
|
657
|
+
*/
|
|
658
|
+
bitRate: number;
|
|
659
|
+
}[]
|
|
660
|
+
| null;
|
|
661
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
const useEventListener = <TEvent extends string, Thandler>(
|
|
4
|
+
emitter: { on(event: TEvent, callback: Thandler): () => void } | null,
|
|
5
|
+
eventName: TEvent,
|
|
6
|
+
handler: any,
|
|
7
|
+
isCurrent?: () => boolean,
|
|
8
|
+
) => {
|
|
9
|
+
const latest = useRef(handler);
|
|
10
|
+
latest.current = handler;
|
|
11
|
+
const enabled = Boolean(handler);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!emitter || !enabled) return;
|
|
14
|
+
const callback = (...args: unknown[]) => {
|
|
15
|
+
if (isCurrent && !isCurrent()) return;
|
|
16
|
+
latest.current?.(...args);
|
|
17
|
+
};
|
|
18
|
+
return emitter.on(eventName, callback as Thandler);
|
|
19
|
+
}, [emitter, eventName, enabled, isCurrent]);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default useEventListener;
|