expo-pro-video-editor 0.1.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/.prettierrc +8 -0
- package/LICENSE +67 -0
- package/NOTICE.md +250 -0
- package/README.md +70 -0
- package/android/build.gradle +35 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/provideoeditor/ExpoProVideoEditorModule.kt +163 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/core/constants/LoggingConstants.kt +4 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/EffectsProcessor.kt +66 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/RenderVideo.kt +1089 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyAnimation.kt +290 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyBitrate.kt +110 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyBlur.kt +27 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyChromaKey.kt +58 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyClipTransition.kt +88 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyColorMatrix.kt +144 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyComposition.kt +86 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyCrop.kt +104 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyFlip.kt +27 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyFrameRate.kt +28 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyImageLayer.kt +397 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyOpacity.kt +24 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyPlaybackSpeed.kt +35 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyRotation.kt +26 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyScale.kt +29 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/AudioPreRenderer.kt +492 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/AudioSequenceBuilder.kt +144 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/BitrateCapPolicy.kt +45 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ChromaKeyEffect.kt +321 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ChromaKeyMath.kt +90 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ClipTransitionGeometry.kt +160 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ClipTransitionRenderer.kt +1002 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/CompositionBuilder.kt +166 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ConfigurableInAppMp4Muxer.kt +137 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/EncoderFailureClassifier.kt +71 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/GifDecoder.kt +76 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/LayeredCompositionBuilder.kt +556 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/MediaInfoExtractor.kt +364 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ResilientVideoEncoderFactory.kt +307 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoCompositionTransformation.kt +192 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoEncoderConfig.kt +179 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoGlobalTrimCalculator.kt +147 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoReverser.kt +1044 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoSequenceBuilder.kt +905 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoTimelineDurationCalculator.kt +19 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoTranscoder.kt +242 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VolumeAudioProcessor.kt +82 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VolumeControlAudioMixer.kt +182 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/models/CodecResourceExhaustedException.kt +24 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/models/RenderConfig.kt +572 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/models/RenderJobHandle.kt +45 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/models/VideoEncoderConfigurationException.kt +20 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/utils/RotatedVideoDimensions.kt +68 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/features/render/utils/VideoMimeUtils.kt +36 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/shared/concurrency/ExportGate.kt +102 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/shared/logging/PluginLog.kt +112 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/shared/media/EncodedImage.kt +101 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/shared/media/ImageOrientation.kt +265 -0
- package/android/src/main/java/expo/modules/provideoeditor/src/shared/media/PcmRangeDecoder.kt +239 -0
- package/build/ExpoProVideoEditor.types.d.ts +125 -0
- package/build/ExpoProVideoEditor.types.d.ts.map +1 -0
- package/build/ExpoProVideoEditor.types.js +8 -0
- package/build/ExpoProVideoEditor.types.js.map +1 -0
- package/build/ExpoProVideoEditorModule.d.ts +20 -0
- package/build/ExpoProVideoEditorModule.d.ts.map +1 -0
- package/build/ExpoProVideoEditorModule.js +3 -0
- package/build/ExpoProVideoEditorModule.js.map +1 -0
- package/build/ExpoProVideoEditorModule.web.d.ts +9 -0
- package/build/ExpoProVideoEditorModule.web.d.ts.map +1 -0
- package/build/ExpoProVideoEditorModule.web.js +16 -0
- package/build/ExpoProVideoEditorModule.web.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +5 -0
- package/build/index.js.map +1 -0
- package/bun.lock +2132 -0
- package/eslint.config.cjs +5 -0
- package/expo-module.config.json +9 -0
- package/ios/ExpoProVideoEditor.podspec +24 -0
- package/ios/ExpoProVideoEditorModule.swift +131 -0
- package/ios/src/features/render/RenderVideo.swift +1087 -0
- package/ios/src/features/render/helpers/ApplyAnimation.swift +198 -0
- package/ios/src/features/render/helpers/ApplyBitrate.swift +112 -0
- package/ios/src/features/render/helpers/ApplyBlur.swift +23 -0
- package/ios/src/features/render/helpers/ApplyChromaKey.swift +175 -0
- package/ios/src/features/render/helpers/ApplyColorMatrix.swift +100 -0
- package/ios/src/features/render/helpers/ApplyComposition.swift +45 -0
- package/ios/src/features/render/helpers/ApplyCrop.swift +53 -0
- package/ios/src/features/render/helpers/ApplyFlip.swift +24 -0
- package/ios/src/features/render/helpers/ApplyImageLayer.swift +65 -0
- package/ios/src/features/render/helpers/ApplyPlaybackSpeed.swift +57 -0
- package/ios/src/features/render/helpers/ApplyRotation.swift +32 -0
- package/ios/src/features/render/helpers/ApplyScale.swift +30 -0
- package/ios/src/features/render/helpers/AudioPreRenderer.swift +352 -0
- package/ios/src/features/render/helpers/AudioReverser.swift +308 -0
- package/ios/src/features/render/helpers/AudioSequenceBuilder.swift +162 -0
- package/ios/src/features/render/helpers/BitrateCapPolicy.swift +77 -0
- package/ios/src/features/render/helpers/BitrateCappedExporter.swift +501 -0
- package/ios/src/features/render/helpers/ClipTransitionGeometry.swift +111 -0
- package/ios/src/features/render/helpers/ClipTransitionRenderer.swift +397 -0
- package/ios/src/features/render/helpers/CompositionBuilder.swift +464 -0
- package/ios/src/features/render/helpers/DecodeOrientedImage.swift +60 -0
- package/ios/src/features/render/helpers/LayeredCompositionBuilder.swift +371 -0
- package/ios/src/features/render/helpers/MediaInfoExtractor.swift +381 -0
- package/ios/src/features/render/helpers/TrackEndTrimmer.swift +112 -0
- package/ios/src/features/render/helpers/VideoSequenceBuilder.swift +555 -0
- package/ios/src/features/render/helpers/VideoTranscoder.swift +283 -0
- package/ios/src/features/render/models/RenderConfig.swift +586 -0
- package/ios/src/features/render/models/VideoClip.swift +81 -0
- package/ios/src/features/render/models/VideoCompositorConfig.swift +78 -0
- package/ios/src/features/render/utils/VideoCompositor.swift +1014 -0
- package/ios/src/features/render/utils/VideoMimeUtils.swift +24 -0
- package/ios/src/shared/EncodedImage.swift +75 -0
- package/ios/src/shared/ExportGate.swift +79 -0
- package/ios/src/shared/ExportSessionDriver.swift +185 -0
- package/ios/src/shared/ExportSessionGuard.swift +85 -0
- package/ios/src/shared/ExportWatchdog.swift +198 -0
- package/ios/src/shared/LoggingConstants.swift +7 -0
- package/ios/src/shared/PluginLog.swift +107 -0
- package/package.json +59 -0
- package/src/ExpoProVideoEditor.types.ts +148 -0
- package/src/ExpoProVideoEditorModule.ts +22 -0
- package/src/ExpoProVideoEditorModule.web.ts +22 -0
- package/src/index.ts +4 -0
- package/tsconfig.json +28 -0
package/.prettierrc
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, manuel2u
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
Portions of this software (native iOS/Android video composition logic under
|
|
33
|
+
ios/ and android/) are adapted from the pro_video_editor project:
|
|
34
|
+
|
|
35
|
+
https://github.com/hm21/pro_video_editor
|
|
36
|
+
|
|
37
|
+
BSD 3-Clause License
|
|
38
|
+
|
|
39
|
+
Copyright (c) 2025, Alex Frei
|
|
40
|
+
|
|
41
|
+
Redistribution and use in source and binary forms, with or without
|
|
42
|
+
modification, are permitted provided that the following conditions are met:
|
|
43
|
+
|
|
44
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
45
|
+
this list of conditions and the following disclaimer.
|
|
46
|
+
|
|
47
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
48
|
+
notice, this list of conditions and the following disclaimer in the
|
|
49
|
+
documentation and/or other materials provided with the distribution.
|
|
50
|
+
|
|
51
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
52
|
+
contributors may be used to endorse or promote products derived from
|
|
53
|
+
this software without specific prior written permission.
|
|
54
|
+
|
|
55
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
56
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
57
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
58
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
59
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
60
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
61
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
62
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
63
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
64
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
65
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
66
|
+
|
|
67
|
+
See NOTICE.md for a per-file breakdown of adapted vs. original code.
|
package/NOTICE.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Third-Party Notices
|
|
2
|
+
|
|
3
|
+
This package's native video composition engine (trim, color filters, image/GIF
|
|
4
|
+
overlays, audio mixing, transitions, thumbnail/waveform generation) is adapted
|
|
5
|
+
from [`pro_video_editor`](https://github.com/hm21/pro_video_editor) by Alex
|
|
6
|
+
Frei, licensed under BSD-3-Clause. See `LICENSE` for the full original license
|
|
7
|
+
text alongside this package's own.
|
|
8
|
+
|
|
9
|
+
`pro_video_editor` is a Flutter plugin. Its Dart-facing API and Flutter
|
|
10
|
+
plugin-registration glue (`MethodChannel`/`Pigeon`/`EventChannel` boundary
|
|
11
|
+
code) do not apply to this package and were not reused — this package
|
|
12
|
+
implements its own JS-facing API via the Expo Modules API. The native
|
|
13
|
+
iOS (Swift/AVFoundation) and Android (Kotlin/Media3 Transformer) composition
|
|
14
|
+
logic underneath that boundary — the actual video-processing implementation —
|
|
15
|
+
is what this package adapts.
|
|
16
|
+
|
|
17
|
+
As the port progresses, this file will track which native source files were
|
|
18
|
+
adapted from `pro_video_editor` versus written new for this package, so the
|
|
19
|
+
attribution stays accurate and specific rather than a blanket claim over the
|
|
20
|
+
whole codebase.
|
|
21
|
+
|
|
22
|
+
## iOS — adapted from `pro_video_editor`
|
|
23
|
+
|
|
24
|
+
| This package | `pro_video_editor` source | Notes |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `ios/src/shared/EncodedImage.swift` | `darwin/.../shared/core/models/EncodedImage.swift` | `FlutterStandardTypedData` unwrap removed — Expo Modules API bridges JS binary data to `Data` directly. |
|
|
27
|
+
| `ios/src/features/render/models/VideoClip.swift` | `darwin/.../render/models/VideoClip.swift` | Verbatim; doc comments referencing "the platform channel" reworded to "the JS boundary". |
|
|
28
|
+
| `ios/src/features/render/models/VideoCompositorConfig.swift` | `darwin/.../render/models/VideoCompositorConfig.swift` | Verbatim, no changes. |
|
|
29
|
+
| `ios/src/features/render/models/RenderConfig.swift` | `darwin/.../render/models/RenderConfig.swift` | `#if os(macOS) import FlutterMacOS #else import Flutter #endif` guard removed — unused by the file's actual logic. |
|
|
30
|
+
| `ios/src/shared/LoggingConstants.swift` | `darwin/.../shared/core/constants/LoggingConstants.swift` | Verbatim; package tag renamed `ProVideoEditor` → `ExpoProVideoEditor`. |
|
|
31
|
+
| `ios/src/shared/PluginLog.swift` | `darwin/.../shared/logging/PluginLog.swift` | Verbatim logic; doc comments referencing "Dart"/"Flutter"/the platform-channel log level reworded for a JS/Expo audience. |
|
|
32
|
+
| `ios/src/features/render/helpers/ApplyRotation.swift` | `darwin/.../render/helpers/ApplyRotation.swift` | Verbatim, no changes. |
|
|
33
|
+
| `ios/src/features/render/helpers/ApplyFlip.swift` | `darwin/.../render/helpers/ApplyFlip.swift` | Verbatim, no changes. |
|
|
34
|
+
| `ios/src/features/render/helpers/ApplyCrop.swift` | `darwin/.../render/helpers/ApplyCrop.swift` | Verbatim, no changes. |
|
|
35
|
+
| `ios/src/features/render/helpers/ApplyScale.swift` | `darwin/.../render/helpers/ApplyScale.swift` | Verbatim, no changes. |
|
|
36
|
+
| `ios/src/features/render/helpers/ApplyBitrate.swift` | `darwin/.../render/helpers/ApplyBitrate.swift` | Verbatim, no changes. |
|
|
37
|
+
| `ios/src/features/render/helpers/ApplyPlaybackSpeed.swift` | `darwin/.../render/helpers/ApplyPlaybackSpeed.swift` | Verbatim, no changes. References `CustomVideoCompositionInstruction`, ported separately. |
|
|
38
|
+
| `ios/src/features/render/helpers/ApplyBlur.swift` | `darwin/.../render/helpers/ApplyBlur.swift` | Verbatim, no changes. |
|
|
39
|
+
| `ios/src/features/render/helpers/ApplyColorMatrix.swift` | `darwin/.../render/helpers/ApplyColorMatrix.swift` | Verbatim, no changes. |
|
|
40
|
+
| `ios/src/features/render/helpers/MediaInfoExtractor.swift` | `darwin/.../render/helpers/MediaInfoExtractor.swift` | Verbatim, no changes. |
|
|
41
|
+
| `ios/src/features/render/helpers/DecodeOrientedImage.swift` | `darwin/.../render/helpers/DecodeOrientedImage.swift` | Verbatim; one doc comment mentioning "the plugin" reworded to "the module". |
|
|
42
|
+
| `ios/src/features/render/helpers/ApplyAnimation.swift` | `darwin/.../render/helpers/ApplyAnimation.swift` | Verbatim, no changes. |
|
|
43
|
+
| `ios/src/features/render/helpers/ApplyImageLayer.swift` | `darwin/.../render/helpers/ApplyImageLayer.swift` | Verbatim, no changes. |
|
|
44
|
+
| `ios/src/features/render/utils/VideoCompositor.swift` | `darwin/.../render/utils/VideoCompositor.swift` | Verbatim; one doc comment referencing Flutter's `Transform.rotate` reworded to a framework-neutral description of the same clockwise-rotation convention. |
|
|
45
|
+
| `ios/src/features/render/helpers/ApplyChromaKey.swift` | `darwin/.../render/helpers/ApplyChromaKey.swift` | Verbatim, no changes. |
|
|
46
|
+
| `ios/src/features/render/helpers/ClipTransitionGeometry.swift` | `darwin/.../render/helpers/ClipTransitionGeometry.swift` | Verbatim, no changes. |
|
|
47
|
+
| `ios/src/features/render/helpers/ClipTransitionRenderer.swift` | `darwin/.../render/helpers/ClipTransitionRenderer.swift` | Verbatim, no changes. References `ExportSessionDriver`, ported separately. |
|
|
48
|
+
| `ios/src/shared/ExportGate.swift` | `darwin/.../shared/concurrency/ExportGate.swift` | Verbatim, no changes. |
|
|
49
|
+
| `ios/src/shared/ExportSessionGuard.swift` | `darwin/.../shared/concurrency/ExportSessionGuard.swift` | Verbatim; one doc comment referencing "the plugin" reworded to "the module". |
|
|
50
|
+
| `ios/src/shared/ExportWatchdog.swift` | `darwin/.../shared/concurrency/ExportWatchdog.swift` | Verbatim, no changes. |
|
|
51
|
+
| `ios/src/shared/ExportSessionDriver.swift` | `darwin/.../shared/concurrency/ExportSessionDriver.swift` | Verbatim; one doc comment referencing "the plugin" reworded to "the module". (Initially missed in the first pass — caught by the compile-check step, see below.) |
|
|
52
|
+
| `ios/src/features/render/RenderVideo.swift` | `darwin/.../render/RenderVideo.swift` | Verbatim, no changes — this file already exposes a plain Swift closure API (`onProgress`/`onComplete`/`onError`), with no Flutter dependency at all. The Flutter-specific glue lives one layer up, in `ProVideoEditorPlugin.swift`, which is not being ported (see below). |
|
|
53
|
+
| — (not ported) | `darwin/.../shared/core/models/RenderTask.swift` | Deliberately **not** ported. This class exists only to adapt `RenderVideo`'s plain-closure callbacks into Flutter's `FlutterResult`/`FlutterError` method-channel convention. Expo Modules API's `AsyncFunction` (`async throws -> T`) already does this adaptation natively — resolving/rejecting the JS Promise — so this package's own module-glue layer (still to be written) replaces `RenderTask` directly rather than porting a Flutter-shaped wrapper and re-wrapping it again. |
|
|
54
|
+
| `ios/src/features/render/utils/VideoMimeUtils.swift` | `darwin/.../render/utils/VideoMimeUtils.swift` | Verbatim, no changes. |
|
|
55
|
+
| `ios/src/features/render/helpers/BitrateCapPolicy.swift` | `darwin/.../render/helpers/BitrateCapPolicy.swift` | Verbatim, no changes. |
|
|
56
|
+
| `ios/src/features/render/helpers/TrackEndTrimmer.swift` | `darwin/.../render/helpers/TrackEndTrimmer.swift` | Verbatim, no changes. |
|
|
57
|
+
| `ios/src/features/render/helpers/VideoTranscoder.swift` | `darwin/.../render/helpers/VideoTranscoder.swift` | Verbatim, no changes. |
|
|
58
|
+
| `ios/src/features/render/helpers/BitrateCappedExporter.swift` | `darwin/.../render/helpers/BitrateCappedExporter.swift` | Verbatim; internal `DispatchQueue` labels renamed from `ch.waio.pro_video_editor.*` to `dev.expo.provideoeditor.*` (cosmetic — queue labels are diagnostic strings only, not a functional dependency). |
|
|
59
|
+
| `ios/src/features/render/helpers/AudioSequenceBuilder.swift` | `darwin/.../render/helpers/AudioSequenceBuilder.swift` | Verbatim, no changes. |
|
|
60
|
+
| `ios/src/features/render/helpers/AudioPreRenderer.swift` | `darwin/.../render/helpers/AudioPreRenderer.swift` | Verbatim, no changes. |
|
|
61
|
+
| `ios/src/features/render/helpers/AudioReverser.swift` | `darwin/.../render/helpers/AudioReverser.swift` | Verbatim, no changes. |
|
|
62
|
+
| `ios/src/features/render/helpers/VideoSequenceBuilder.swift` | `darwin/.../render/helpers/VideoSequenceBuilder.swift` | Verbatim, no changes. Also carries `VideoCompositionData`, `LayerPlacement`, and `CustomVideoCompositionInstruction`, which live in this same source file upstream. |
|
|
63
|
+
| `ios/src/features/render/helpers/CompositionBuilder.swift` | `darwin/.../render/helpers/CompositionBuilder.swift` | Verbatim, no changes. |
|
|
64
|
+
| `ios/src/features/render/helpers/ApplyComposition.swift` | `darwin/.../render/helpers/ApplyComposition.swift` | Verbatim, no changes. |
|
|
65
|
+
| `ios/src/features/render/helpers/LayeredCompositionBuilder.swift` | `darwin/.../render/helpers/LayeredCompositionBuilder.swift` | Verbatim, no changes. |
|
|
66
|
+
|
|
67
|
+
## iOS port status
|
|
68
|
+
|
|
69
|
+
All 37 native source files that `RenderVideo.render(...)` transitively depends
|
|
70
|
+
on have been ported (models, the full composition pipeline — single-track and
|
|
71
|
+
layered — every effect helper, transitions, audio pre-render/reverse/mix, the
|
|
72
|
+
bitrate-capped exporter, and the concurrency/export-safety layer). None of
|
|
73
|
+
them had any real Flutter dependency once traced past their imports — the only
|
|
74
|
+
two that did (`EncodedImage`'s byte-unwrap, and `RenderTask`, not ported at
|
|
75
|
+
all) are noted above.
|
|
76
|
+
|
|
77
|
+
**Verified**: the full set compiles cleanly as the `ExpoProVideoEditor` pod
|
|
78
|
+
target (`xcodebuild ... -scheme ExpoProVideoEditor -sdk iphonesimulator build`
|
|
79
|
+
→ `BUILD SUCCEEDED`) against the example app. Nothing is wired up to JS yet —
|
|
80
|
+
this only confirms the native composition engine itself is complete and
|
|
81
|
+
self-consistent.
|
|
82
|
+
|
|
83
|
+
## iOS module glue (JS-callable)
|
|
84
|
+
|
|
85
|
+
`ExpoProVideoEditorModule.swift` now exposes `render(config, id)` and
|
|
86
|
+
`cancelRender(id)` as `AsyncFunction`s, plus an `onRenderProgress` event —
|
|
87
|
+
the Expo Modules API equivalent of what `ProVideoEditorPlugin.swift`'s
|
|
88
|
+
`renderVideo`/`cancelTask` method-channel handlers and `RenderTask` did for
|
|
89
|
+
Flutter. Not a line-for-line port (there's no Flutter plugin file to adapt
|
|
90
|
+
here): `AsyncFunction`'s `Promise` argument replaces `RenderTask`'s manual
|
|
91
|
+
`FlutterResult` storage/dedup, and `Module.sendEvent` replaces the
|
|
92
|
+
`FlutterEventChannel`/`StreamHandler` progress-streaming plumbing — both
|
|
93
|
+
handled natively by Expo instead.
|
|
94
|
+
|
|
95
|
+
The render config crosses the JS boundary as a **raw dictionary** (`[String:
|
|
96
|
+
Any]` on the Swift side, a plain object in TS — see `src/ExpoProVideoEditor.types.ts`),
|
|
97
|
+
handed straight to the already-ported `RenderConfig.fromArguments(_:)`
|
|
98
|
+
unchanged, rather than re-modeling `RenderConfig`'s large/deeply-nested shape
|
|
99
|
+
as a second parallel tree of Expo `Record` structs.
|
|
100
|
+
|
|
101
|
+
**Verified**: compiles clean as part of the same `ExpoProVideoEditor` pod
|
|
102
|
+
target build; `tsc --noEmit` and `eslint` both clean on the TS side.
|
|
103
|
+
|
|
104
|
+
**Verified at runtime** on the iOS simulator via the example app's smoke-test
|
|
105
|
+
screen: download a small public clip to a local file, `render()` a 3-second
|
|
106
|
+
trim, resolve real output bytes (~1.9MB from a 5s/1.1MB source). This also
|
|
107
|
+
caught and fixed a real bug — every error this module threw (not just render
|
|
108
|
+
failures) was reaching JS as the literal string `"undefined reason"` instead
|
|
109
|
+
of its actual message, because `Exception`'s JS-visible message reads its
|
|
110
|
+
`reason` property (always `debugDescription`), not the `description` passed
|
|
111
|
+
to `Exception(name:description:code:)`. Fixed by subclassing `Exception` to
|
|
112
|
+
override `reason` — see the `fix(ios)` commit for detail.
|
|
113
|
+
|
|
114
|
+
## Android — adapted from `pro_video_editor`
|
|
115
|
+
|
|
116
|
+
| This package | `pro_video_editor` source | Notes |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `android/.../src/core/constants/LoggingConstants.kt` | `android/.../src/core/constants/LoggingConstants.kt` | Only `RENDER_TAG` ported (render scope only); tag renamed `ProVideoEditor` → `ExpoProVideoEditor`. Upstream declares this in Kotlin's default package; given an explicit package here (see below). |
|
|
119
|
+
| `android/.../src/shared/logging/PluginLog.kt` | `android/.../src/shared/logging/PluginLog.kt` | Verbatim logic; the reflective `BuildConfig` class lookup repointed at this package's own `BuildConfig`; doc comments referencing "Dart"/"Flutter" reworded for a JS/Expo audience. |
|
|
120
|
+
| `android/.../src/shared/concurrency/ExportGate.kt` | `android/.../src/shared/concurrency/ExportGate.kt` | Verbatim; doc comments reworded ("the split and render pipelines" → "the render pipeline", "the MethodChannel handler" → "the Expo Modules API's `AsyncFunction` handler"). Carries `ExportGateGuard` in the same file, same as upstream. |
|
|
121
|
+
| `android/.../src/shared/media/EncodedImage.kt` | `android/.../src/shared/media/EncodedImage.kt` | Verbatim logic; doc comments referencing "the method channel"/"Dart" reworded to "the JS boundary". |
|
|
122
|
+
| `android/.../src/shared/media/PcmRangeDecoder.kt` | `android/.../src/shared/media/PcmRangeDecoder.kt` | Verbatim, no changes. |
|
|
123
|
+
| `android/.../src/shared/media/ImageOrientation.kt` | `android/.../src/shared/media/ImageOrientation.kt` | Verbatim, no changes. |
|
|
124
|
+
| `android/.../src/features/render/models/RenderConfig.kt` | `android/.../src/features/render/models/RenderConfig.kt` | `fromMethodCall(call: MethodCall)` replaced with `fromArguments(args: Map<String, Any?>)`, reading the same fields directly off the map instead of through `call.argument<T>(key)` — the Kotlin equivalent of the iOS port's `fromMethodCall` → `fromArguments` adaptation. Carries `TransitionConfig`, `VideoClip`, `SegmentTransformConfig`, `LayerConfig`, `CompositionConfig`, `ColorFilterConfig`, `ChromaKeyConfig`, `AudioTrackConfig`, `LayerAnimationConfig`, `ImageLayer` in the same file, same as upstream. |
|
|
125
|
+
| `android/.../src/features/render/models/RenderJobHandle.kt` | `android/.../src/features/render/models/RenderJobHandle.kt` | Verbatim, no changes. |
|
|
126
|
+
| `android/.../src/features/render/models/CodecResourceExhaustedException.kt` | `android/.../src/features/render/models/CodecResourceExhaustedException.kt` | Verbatim, no changes. |
|
|
127
|
+
| `android/.../src/features/render/models/VideoEncoderConfigurationException.kt` | `android/.../src/features/render/models/VideoEncoderConfigurationException.kt` | Verbatim; one doc comment referencing "the Flutter layer" reworded to "this module". |
|
|
128
|
+
| `android/.../src/features/render/utils/VideoMimeUtils.kt` | `android/.../src/features/render/utils/VideoMimeUtils.kt` | Verbatim, no changes. Upstream declares this in Kotlin's default package; given an explicit package here. |
|
|
129
|
+
| `android/.../src/features/render/utils/RotatedVideoDimensions.kt` | `android/.../src/features/render/utils/RotatedVideoDimensions.kt` | Verbatim, no changes. |
|
|
130
|
+
| `android/.../src/features/render/EffectsProcessor.kt` | `android/.../src/features/render/utils/EffectsProcessor.kt` | Verbatim logic; moved to match its own declared package (`...features.render`, not `...features.render.utils` — an upstream directory/package mismatch, not preserved here). |
|
|
131
|
+
| `android/.../src/features/render/helpers/ApplyRotation.kt` | `android/.../src/features/render/helpers/ApplyRotation.kt` | Verbatim logic. Upstream default-package file; given an explicit package plus an added `RENDER_TAG` import. |
|
|
132
|
+
| `android/.../src/features/render/helpers/ApplyFlip.kt` | `android/.../src/features/render/helpers/ApplyFlip.kt` | Same treatment as `ApplyRotation.kt`. |
|
|
133
|
+
| `android/.../src/features/render/helpers/ApplyScale.kt` | `android/.../src/features/render/helpers/ApplyScale.kt` | Same treatment as `ApplyRotation.kt`. |
|
|
134
|
+
| `android/.../src/features/render/helpers/ApplyFrameRate.kt` | `android/.../src/features/render/helpers/ApplyFrameRate.kt` | Same treatment as `ApplyRotation.kt`. |
|
|
135
|
+
| `android/.../src/features/render/helpers/ApplyPlaybackSpeed.kt` | `android/.../src/features/render/helpers/ApplyPlaybackSpeed.kt` | Same treatment as `ApplyRotation.kt`. |
|
|
136
|
+
| `android/.../src/features/render/helpers/ApplyBlur.kt` | `android/.../src/features/render/helpers/ApplyBlur.kt` | Same treatment as `ApplyRotation.kt`. |
|
|
137
|
+
| `android/.../src/features/render/helpers/ApplyColorMatrix.kt` | `android/.../src/features/render/helpers/ApplyColorMatrix.kt` | Same treatment as `ApplyRotation.kt`, plus its `ColorFilterConfig` import repointed at `RenderConfig.kt`. |
|
|
138
|
+
| `android/.../src/features/render/helpers/ApplyBitrate.kt` | `android/.../src/features/render/helpers/ApplyBitrate.kt` | Same treatment as `ApplyRotation.kt`. Carries `BitrateChoice` in the same file, same as upstream. |
|
|
139
|
+
| `android/.../src/features/render/helpers/ApplyCrop.kt` | `android/.../src/features/render/helpers/ApplyCrop.kt` | Same treatment as `ApplyRotation.kt`. |
|
|
140
|
+
| `android/.../src/features/render/helpers/ApplyOpacity.kt` | `android/.../src/features/render/helpers/ApplyOpacity.kt` | Same treatment as `ApplyRotation.kt`. |
|
|
141
|
+
| `android/.../src/features/render/helpers/ApplyChromaKey.kt` | `android/.../src/features/render/helpers/ApplyChromaKey.kt` | Upstream default-package file; given an explicit package plus `RENDER_TAG` and `ChromaKeyConfig` imports (the latter now in `RenderConfig.kt`). References `ChromaKeyEffect`, ported separately. |
|
|
142
|
+
| `android/.../src/features/render/helpers/ChromaKeyMath.kt` | `android/.../src/features/render/helpers/ChromaKeyMath.kt` | Verbatim logic; `ChromaKeyConfig` import repointed at `RenderConfig.kt`. |
|
|
143
|
+
| `android/.../src/features/render/helpers/ChromaKeyEffect.kt` | `android/.../src/features/render/helpers/ChromaKeyEffect.kt` | Verbatim logic; imports repointed (`RENDER_TAG`, `ChromaKeyConfig`, `ImageOrientation`). |
|
|
144
|
+
| `android/.../src/features/render/helpers/ApplyAnimation.kt` | `android/.../src/features/render/helpers/ApplyAnimation.kt` | Verbatim, no changes. Carries `SlideOffset`, `OverlayAnchors`, `AnimatedBitmapOverlay`, and the `applyEasing`/`slideOffset`/`resolveAnchor` helpers, same as upstream. |
|
|
145
|
+
| `android/.../src/features/render/helpers/ApplyImageLayer.kt` | `android/.../src/features/render/helpers/ApplyImageLayer.kt` | Verbatim logic; `RENDER_TAG` import added. Carries `applyTimedImageLayers`, `resolveOpenEndedOutAnimations`, `overlayDecodeSize`, and the bitmap-prep/rotate/unpremultiply helpers, same as upstream. |
|
|
146
|
+
| `android/.../src/features/render/helpers/ApplyClipTransition.kt` | `android/.../src/features/render/helpers/ApplyClipTransition.kt` | Verbatim, no changes. Carries `ClipFadeOverlay`. |
|
|
147
|
+
| `android/.../src/features/render/helpers/GifDecoder.kt` | `android/.../src/features/render/helpers/GifDecoder.kt` | Verbatim, no changes. |
|
|
148
|
+
| `android/.../src/features/render/helpers/ClipTransitionGeometry.kt` | `android/.../src/features/render/helpers/ClipTransitionGeometry.kt` | Verbatim, no changes. |
|
|
149
|
+
| `android/.../src/features/render/helpers/ClipTransitionRenderer.kt` | `android/.../src/features/render/helpers/ClipTransitionRenderer.kt` | Verbatim logic; `RENDER_TAG` import added. |
|
|
150
|
+
| `android/.../src/features/render/helpers/VideoEncoderConfig.kt` | `android/.../src/features/render/helpers/VideoEncoderConfig.kt` | Verbatim, no changes. |
|
|
151
|
+
| `android/.../src/features/render/helpers/VideoGlobalTrimCalculator.kt` | `android/.../src/features/render/helpers/VideoGlobalTrimCalculator.kt` | Verbatim, no changes. |
|
|
152
|
+
| `android/.../src/features/render/helpers/VideoTimelineDurationCalculator.kt` | `android/.../src/features/render/helpers/VideoTimelineDurationCalculator.kt` | Verbatim, no changes. |
|
|
153
|
+
| `android/.../src/features/render/helpers/BitrateCapPolicy.kt` | `android/.../src/features/render/helpers/BitrateCapPolicy.kt` | Verbatim, no changes. |
|
|
154
|
+
| `android/.../src/features/render/helpers/EncoderFailureClassifier.kt` | `android/.../src/features/render/helpers/EncoderFailureClassifier.kt` | Verbatim, no changes. |
|
|
155
|
+
| `android/.../src/features/render/helpers/ResilientVideoEncoderFactory.kt` | `android/.../src/features/render/helpers/ResilientVideoEncoderFactory.kt` | Verbatim logic; `RENDER_TAG` import added, `BitrateChoice`/`resolveBitrateSettings` bare imports dropped now that both are same-package. |
|
|
156
|
+
| `android/.../src/features/render/helpers/MediaInfoExtractor.kt` | `android/.../src/features/render/helpers/MediaInfoExtractor.kt` | Verbatim logic; `RENDER_TAG` import added. |
|
|
157
|
+
| `android/.../src/features/render/helpers/ConfigurableInAppMp4Muxer.kt` | `android/.../src/features/render/helpers/ConfigurableInAppMp4Muxer.kt` | Verbatim, no changes (`androidx.media3.common.Metadata` is a Media3 class, unrelated to this package). |
|
|
158
|
+
| `android/.../src/features/render/helpers/VolumeAudioProcessor.kt` | `android/.../src/features/render/helpers/VolumeAudioProcessor.kt` | Verbatim logic; `RENDER_TAG` import added. |
|
|
159
|
+
| `android/.../src/features/render/helpers/VolumeControlAudioMixer.kt` | `android/.../src/features/render/helpers/VolumeControlAudioMixer.kt` | Verbatim logic; `RENDER_TAG` import added. |
|
|
160
|
+
| `android/.../src/features/render/helpers/AudioSequenceBuilder.kt` | `android/.../src/features/render/helpers/AudioSequenceBuilder.kt` | Verbatim logic; `RENDER_TAG` import added. |
|
|
161
|
+
| `android/.../src/features/render/helpers/AudioPreRenderer.kt` | `android/.../src/features/render/helpers/AudioPreRenderer.kt` | Verbatim logic; `RENDER_TAG`/`PcmRangeDecoder` imports repointed. |
|
|
162
|
+
| `android/.../src/features/render/helpers/VideoReverser.kt` | `android/.../src/features/render/helpers/VideoReverser.kt` | Verbatim, no changes beyond the `RENDER_TAG` import. |
|
|
163
|
+
| `android/.../src/features/render/helpers/VideoTranscoder.kt` | `android/.../src/features/render/helpers/VideoTranscoder.kt` | Verbatim logic; `RENDER_TAG` import added. |
|
|
164
|
+
| `android/.../src/features/render/helpers/VideoCompositionTransformation.kt` | `android/.../src/features/render/helpers/VideoCompositionTransformation.kt` | Verbatim; one doc comment referencing "the Flutter side" reworded to "the JS side". |
|
|
165
|
+
| `android/.../src/features/render/helpers/VideoSequenceBuilder.kt` | `android/.../src/features/render/helpers/VideoSequenceBuilder.kt` | Verbatim logic; imports repointed (`RENDER_TAG`, `applyChromaKey`, `applyScale`, and the `models`/`utils`/`shared` cross-package imports). Carries `ImageLayerConfig` and `CropConfig` nested types, same as upstream. |
|
|
166
|
+
| `android/.../src/features/render/helpers/CompositionBuilder.kt` | `android/.../src/features/render/helpers/CompositionBuilder.kt` | Verbatim logic; `RENDER_TAG`/`AudioTrackConfig`/`RenderConfig` imports repointed. |
|
|
167
|
+
| `android/.../src/features/render/helpers/ApplyComposition.kt` | `android/.../src/features/render/helpers/ApplyComposition.kt` | Verbatim logic; `RenderConfig` import repointed. Carries `CompositionResult`, same as upstream. |
|
|
168
|
+
| `android/.../src/features/render/helpers/LayeredCompositionBuilder.kt` | `android/.../src/features/render/helpers/LayeredCompositionBuilder.kt` | Verbatim logic; imports repointed (`RENDER_TAG`, `applyChromaKey`, and the `models` cross-package imports). |
|
|
169
|
+
| `android/.../src/features/render/RenderVideo.kt` | `android/.../src/features/render/RenderVideo.kt` | Verbatim logic; imports repointed (`RENDER_TAG`, `mapFormatToMimeType`, and every `models`/`helpers`/`shared` cross-package import); one doc comment referencing "the Flutter layer" reworded to "this module". Already exposes a plain Kotlin callback API (`onProgress`/`onComplete`/`onError`) with no Flutter dependency at all — the Flutter-specific glue lives one layer up, in `ProVideoEditorPlugin.kt`, which is not being ported (see below). |
|
|
170
|
+
| — (not ported) | `android/.../src/features/render/models/RenderTask.kt` | Deliberately **not** ported — same rationale as iOS's `RenderTask.swift`: this class only adapts `RenderVideo`'s plain-callback API into Flutter's method-channel `Result`/error convention, which Expo's `AsyncFunction`/`Promise` already does natively. |
|
|
171
|
+
|
|
172
|
+
## Android port status
|
|
173
|
+
|
|
174
|
+
All 45 native source files that `RenderVideo.render(...)` transitively depends
|
|
175
|
+
on have been ported (models, the full composition pipeline — single-track and
|
|
176
|
+
layered — every effect helper, transitions, audio pre-render/reverse/mix, the
|
|
177
|
+
resilient encoder factory, and the concurrency/logging/media shared layer).
|
|
178
|
+
None had any real Flutter dependency once traced past their imports — the only
|
|
179
|
+
one that did (`RenderConfig.fromMethodCall`, and `RenderTask.kt`, not ported at
|
|
180
|
+
all) is noted above. A handful of files (`ApplyAnimation`, `ApplyClipTransition`,
|
|
181
|
+
`ApplyCrop`, `ApplyImageLayer`, `ApplyOpacity`, `GifDecoder`, `ApplyComposition`)
|
|
182
|
+
were missed in the first dependency-discovery pass — same-package references
|
|
183
|
+
with no `import` line are invisible to an import-based scan — and were caught
|
|
184
|
+
by the compile-check step below, the same role the missing `ExportSessionDriver.swift`
|
|
185
|
+
played on iOS.
|
|
186
|
+
|
|
187
|
+
Roughly a third of upstream's `helpers/` files (`ApplyRotation`, `ApplyFlip`,
|
|
188
|
+
`ApplyScale`, `ApplyFrameRate`, `ApplyPlaybackSpeed`, `ApplyBlur`,
|
|
189
|
+
`ApplyChromaKey`, `ApplyColorMatrix`, `ApplyBitrate`, `ApplyCrop`,
|
|
190
|
+
`ApplyOpacity`, `VideoMimeUtils`) declare no package at all upstream (Kotlin's
|
|
191
|
+
default/unnamed package), relying on that for same-package access to
|
|
192
|
+
`RENDER_TAG` and each other despite living in nested directories — an upstream
|
|
193
|
+
inconsistency, not preserved here. Every ported file gets an explicit package
|
|
194
|
+
matching its destination directory, with whatever new imports that requires.
|
|
195
|
+
|
|
196
|
+
**Verified**: the full set compiles cleanly via
|
|
197
|
+
`./gradlew :expo-pro-video-editor:compileDebugKotlin` (`BUILD SUCCESSFUL`)
|
|
198
|
+
against the example app's generated Android project. Only pre-existing
|
|
199
|
+
upstream deprecation warnings surface (`SpeedChangeEffect`,
|
|
200
|
+
`EditedMediaItemSequence.Builder(vararg)`, `Movie`) — none introduced by the
|
|
201
|
+
port.
|
|
202
|
+
|
|
203
|
+
**Verified at runtime** on an Android emulator (API 36, Pixel 7) via the same
|
|
204
|
+
smoke-test screen used for iOS: download a small public clip, `render()` a
|
|
205
|
+
3-second trim, resolve real output bytes (~1.08MB from the same 5s/1.1MB
|
|
206
|
+
source). This caught a real bug on the first attempt — see the module glue
|
|
207
|
+
section below.
|
|
208
|
+
|
|
209
|
+
## Android module glue (JS-callable)
|
|
210
|
+
|
|
211
|
+
`ExpoProVideoEditorModule.kt` exposes `render(config, id)` and `cancelRender(id)`
|
|
212
|
+
as `AsyncFunction`s (Kotlin's explicit-`Promise`-parameter form, matching
|
|
213
|
+
`ExpoProVideoEditorModule.swift`'s pattern) plus an `onRenderProgress` event —
|
|
214
|
+
the same role `ProVideoEditorPlugin.kt`'s `renderVideo`/`cancelTask`
|
|
215
|
+
method-channel handlers and `RenderTask.kt` played for Flutter. Kotlin's
|
|
216
|
+
`CodedException` needed no `Exception.reason`-style workaround: unlike iOS's
|
|
217
|
+
`Exception`, whose JS-visible message reads a separate `reason` property (see
|
|
218
|
+
the iOS `fix(ios)` commit), `CodedException`'s `message` constructor argument
|
|
219
|
+
is exactly what reaches JS.
|
|
220
|
+
|
|
221
|
+
The render config crosses the JS boundary as a **raw map** (`Map<String,
|
|
222
|
+
Any?>` on the Kotlin side, a plain object in TS), handed straight to the
|
|
223
|
+
already-ported `RenderConfig.fromArguments(_:)` unchanged — the same choice
|
|
224
|
+
made on iOS, rather than re-modeling `RenderConfig`'s shape as a second
|
|
225
|
+
parallel tree of Expo `Record` structs.
|
|
226
|
+
|
|
227
|
+
**Verified**: compiles clean as part of the same
|
|
228
|
+
`:expo-pro-video-editor:compileDebugKotlin` build, and confirmed working at
|
|
229
|
+
runtime (see above) after one fix: Expo Modules API dispatches an
|
|
230
|
+
`AsyncFunction` on a background queue by default, but Media3's `Transformer`
|
|
231
|
+
requires every call — including `addListener`, invoked from `RenderVideo`'s
|
|
232
|
+
own main-Looper `post` — on the exact thread that created it. Building the
|
|
233
|
+
`Transformer` on the default background queue crashed the first render with
|
|
234
|
+
`IllegalStateException: Transformer is accessed on the wrong thread` the
|
|
235
|
+
moment the main-thread callback touched it. Fixed by chaining
|
|
236
|
+
`.runOnQueue(Queues.MAIN)` onto both `AsyncFunction`s so the whole call stays
|
|
237
|
+
on the single thread Media3 expects throughout — see the `fix(android)`
|
|
238
|
+
commit for detail. No equivalent issue exists on iOS: `AVFoundation`'s
|
|
239
|
+
`Transformer`-equivalent objects aren't thread-affine the same way, and the
|
|
240
|
+
Expo Modules API's Swift side already runs `AsyncFunction` bodies
|
|
241
|
+
consistently.
|
|
242
|
+
|
|
243
|
+
## Remaining work
|
|
244
|
+
|
|
245
|
+
The TS-facing API surface for anything beyond trim/basic effects (filters,
|
|
246
|
+
image/text overlays, audio mixing, transitions) is modeled in `RenderConfig`
|
|
247
|
+
on both platforms already and compiles, but only the trim path has been
|
|
248
|
+
exercised at runtime so far on either platform — filters, image layers, audio
|
|
249
|
+
mixing, and transitions are still untested end-to-end on iOS and Android
|
|
250
|
+
alike.
|
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# expo-pro-video-editor
|
|
2
|
+
|
|
3
|
+
A native video composition module for Expo/React Native — trim, crop, color
|
|
4
|
+
filters, image/GIF/text overlays, and multi-track audio mixing, exported to a
|
|
5
|
+
single video file.
|
|
6
|
+
|
|
7
|
+
Built on the same native frameworks as any serious video editor: iOS uses
|
|
8
|
+
`AVFoundation`/`AVMutableComposition` + `CoreImage`, Android uses
|
|
9
|
+
`androidx.media3.transformer` + `MediaCodec`/`MediaMuxer`. No FFmpeg
|
|
10
|
+
dependency (FFmpegKit, the library most RN video-processing packages relied
|
|
11
|
+
on, was retired in 2025/2026), no paid SDK, no license fee.
|
|
12
|
+
|
|
13
|
+
## Origin
|
|
14
|
+
|
|
15
|
+
The native composition logic is adapted from
|
|
16
|
+
[`pro_video_editor`](https://github.com/hm21/pro_video_editor), a Flutter
|
|
17
|
+
plugin by Alex Frei that does the same job for Flutter apps via a
|
|
18
|
+
`MethodChannel` bridge. Flutter and Expo/React Native don't share a plugin
|
|
19
|
+
protocol, so that bridging layer can't be reused directly — but the actual
|
|
20
|
+
video-processing code underneath it (AVFoundation compositions on iOS, Media3
|
|
21
|
+
Transformer on Android) is plain native code that doesn't know or care which
|
|
22
|
+
JS framework called it. This package keeps that native logic and replaces
|
|
23
|
+
only the bridge with the Expo Modules API.
|
|
24
|
+
|
|
25
|
+
See `LICENSE` and `NOTICE.md` for the full attribution — this is a BSD-3-Clause
|
|
26
|
+
derivative work.
|
|
27
|
+
|
|
28
|
+
## Status
|
|
29
|
+
|
|
30
|
+
Early, in-progress port. Not yet published to npm. Track progress against
|
|
31
|
+
`pro_video_editor`'s feature set in `NOTICE.md`.
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
|
|
35
|
+
This repo is a standard Expo native module layout:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
src/ TypeScript API surface (what consumers import)
|
|
39
|
+
ios/ Swift native implementation
|
|
40
|
+
android/ Kotlin native implementation
|
|
41
|
+
example/ A runnable Expo app for testing the module in isolation
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Running the example app
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
bun install
|
|
48
|
+
cd example
|
|
49
|
+
bun install
|
|
50
|
+
bun ios # or: bun android
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`example/` autolinks this module from `..` (see `example/package.json`'s
|
|
54
|
+
`expo.autolinking.nativeModulesDir`), so changes to `src/`, `ios/`, or
|
|
55
|
+
`android/` are picked up without publishing anything.
|
|
56
|
+
|
|
57
|
+
Beyond testing individual native features, `example/` also doubles as a
|
|
58
|
+
staging ground for the actual clip-editor screen UI (trim scrubber, filter
|
|
59
|
+
tray, text/sticker tool, audio picker) being built against the Shop Society
|
|
60
|
+
Figma designs — proving the screen and the native module out together here,
|
|
61
|
+
independent of `shop-society-mobile`'s app shell, before porting the finished
|
|
62
|
+
screen over.
|
|
63
|
+
|
|
64
|
+
### Scripts
|
|
65
|
+
|
|
66
|
+
- `bun run build` — compile `src/` to `build/`
|
|
67
|
+
- `bun run lint` — lint `src/`
|
|
68
|
+
- `bun run test` — run tests
|
|
69
|
+
- `bun run open:ios` / `bun run open:android` — open the example app's native
|
|
70
|
+
project in Xcode / Android Studio
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
plugins {
|
|
2
|
+
id 'com.android.library'
|
|
3
|
+
id 'expo-module-gradle-plugin'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
group = 'expo.modules.provideoeditor'
|
|
7
|
+
version = '0.1.0'
|
|
8
|
+
|
|
9
|
+
android {
|
|
10
|
+
namespace "expo.modules.provideoeditor"
|
|
11
|
+
defaultConfig {
|
|
12
|
+
versionCode 1
|
|
13
|
+
versionName "0.1.0"
|
|
14
|
+
minSdk 24
|
|
15
|
+
}
|
|
16
|
+
lintOptions {
|
|
17
|
+
abortOnError false
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dependencies {
|
|
22
|
+
def media3_version = "1.10.1"
|
|
23
|
+
implementation "androidx.media3:media3-common:$media3_version"
|
|
24
|
+
implementation "androidx.media3:media3-transformer:$media3_version"
|
|
25
|
+
implementation "androidx.media3:media3-effect:$media3_version"
|
|
26
|
+
implementation "androidx.media3:media3-muxer:$media3_version"
|
|
27
|
+
|
|
28
|
+
// EXIF metadata for image-overlay orientation (ImageOrientation.kt).
|
|
29
|
+
// Preferred over android.media.ExifInterface: it covers HEIF/AVIF/WebP/PNG
|
|
30
|
+
// as well as JPEG.
|
|
31
|
+
implementation "androidx.exifinterface:exifinterface:1.4.2"
|
|
32
|
+
|
|
33
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3"
|
|
34
|
+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
|
|
35
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
package expo.modules.provideoeditor
|
|
2
|
+
|
|
3
|
+
import androidx.media3.common.util.UnstableApi
|
|
4
|
+
import expo.modules.kotlin.exception.CodedException
|
|
5
|
+
import expo.modules.kotlin.functions.Queues
|
|
6
|
+
import expo.modules.kotlin.modules.Module
|
|
7
|
+
import expo.modules.kotlin.modules.ModuleDefinition
|
|
8
|
+
import expo.modules.kotlin.Promise
|
|
9
|
+
import expo.modules.provideoeditor.src.features.render.RenderVideo
|
|
10
|
+
import expo.modules.provideoeditor.src.features.render.models.RenderConfig
|
|
11
|
+
import expo.modules.provideoeditor.src.features.render.models.RenderJobHandle
|
|
12
|
+
import expo.modules.provideoeditor.src.shared.logging.PluginLog
|
|
13
|
+
|
|
14
|
+
@UnstableApi
|
|
15
|
+
class ExpoProVideoEditorModule : Module() {
|
|
16
|
+
/**
|
|
17
|
+
* Active render jobs, keyed by the caller-supplied task id.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors `ProVideoEditorPlugin.activeRenderTasks`: tracked so a second
|
|
20
|
+
* `render` call reusing the same `id` is rejected instead of silently
|
|
21
|
+
* racing another job, and so `cancelRender` can find the job to stop.
|
|
22
|
+
*/
|
|
23
|
+
private val activeRenderHandles = mutableMapOf<String, RenderJobHandle>()
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether the task for `id` was cancelled by an explicit `cancelRender`
|
|
27
|
+
* call, as opposed to a pipeline cancelling itself (a stalled-export
|
|
28
|
+
* watchdog, a refused start). Both surface the same way, but only the
|
|
29
|
+
* former is a genuine user-requested cancel.
|
|
30
|
+
*/
|
|
31
|
+
private val explicitlyCancelled = mutableSetOf<String>()
|
|
32
|
+
|
|
33
|
+
override fun definition() = ModuleDefinition {
|
|
34
|
+
Name("ExpoProVideoEditor")
|
|
35
|
+
|
|
36
|
+
Events("onRenderProgress")
|
|
37
|
+
|
|
38
|
+
// Expo Modules API dispatches an AsyncFunction on a background queue by
|
|
39
|
+
// default. Media3's Transformer binds to whichever thread creates it and
|
|
40
|
+
// then requires every later call (including addListener from the
|
|
41
|
+
// RenderVideo pipeline's own main-Looper post) on that same thread — a
|
|
42
|
+
// Transformer built on the default background queue crashes the first
|
|
43
|
+
// time a main-thread callback touches it ("Transformer is accessed on the
|
|
44
|
+
// wrong thread"). Forcing this function onto the main queue keeps the
|
|
45
|
+
// whole render() call on the same thread Media3 expects throughout.
|
|
46
|
+
AsyncFunction("render") { args: Map<String, Any?>, id: String, promise: Promise ->
|
|
47
|
+
if (id.isEmpty()) {
|
|
48
|
+
promise.reject(ExpoProVideoEditorException.invalidArguments("Missing task id"))
|
|
49
|
+
return@AsyncFunction
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (activeRenderHandles.containsKey(id)) {
|
|
53
|
+
promise.reject(ExpoProVideoEditorException.taskAlreadyRunning(id))
|
|
54
|
+
return@AsyncFunction
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
val config: RenderConfig
|
|
58
|
+
try {
|
|
59
|
+
config = RenderConfig.fromArguments(args)
|
|
60
|
+
} catch (e: Exception) {
|
|
61
|
+
promise.reject(
|
|
62
|
+
ExpoProVideoEditorException.invalidArguments(
|
|
63
|
+
"Invalid render configuration: ${e.message}"
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
return@AsyncFunction
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
val context = appContext.reactContext
|
|
70
|
+
if (context == null) {
|
|
71
|
+
promise.reject(
|
|
72
|
+
ExpoProVideoEditorException.invalidArguments("Application context is not available")
|
|
73
|
+
)
|
|
74
|
+
return@AsyncFunction
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
sendEvent("onRenderProgress", mapOf("id" to id, "progress" to 0.0))
|
|
78
|
+
|
|
79
|
+
val renderVideo = RenderVideo(context)
|
|
80
|
+
val handle = renderVideo.render(
|
|
81
|
+
config = config,
|
|
82
|
+
onProgress = { progress ->
|
|
83
|
+
sendEvent("onRenderProgress", mapOf("id" to id, "progress" to progress))
|
|
84
|
+
},
|
|
85
|
+
onComplete = { outputData ->
|
|
86
|
+
sendEvent("onRenderProgress", mapOf("id" to id, "progress" to 1.0))
|
|
87
|
+
activeRenderHandles.remove(id)
|
|
88
|
+
explicitlyCancelled.remove(id)
|
|
89
|
+
promise.resolve(outputData)
|
|
90
|
+
},
|
|
91
|
+
onError = { error ->
|
|
92
|
+
PluginLog.e("ExpoProVideoEditor", "Render failed: ${error.message}", error)
|
|
93
|
+
activeRenderHandles.remove(id)
|
|
94
|
+
val wasCancelled = explicitlyCancelled.remove(id)
|
|
95
|
+
promise.reject(
|
|
96
|
+
ExpoProVideoEditorException.renderFailed(canceled = wasCancelled, error = error)
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
activeRenderHandles[id] = handle
|
|
102
|
+
}.runOnQueue(Queues.MAIN)
|
|
103
|
+
|
|
104
|
+
AsyncFunction("cancelRender") { id: String, promise: Promise ->
|
|
105
|
+
if (id.isEmpty()) {
|
|
106
|
+
promise.reject(ExpoProVideoEditorException.invalidArguments("Missing task id"))
|
|
107
|
+
return@AsyncFunction
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
val handle = activeRenderHandles[id]
|
|
111
|
+
if (handle == null) {
|
|
112
|
+
promise.reject(ExpoProVideoEditorException.taskNotFound(id))
|
|
113
|
+
return@AsyncFunction
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
explicitlyCancelled.add(id)
|
|
117
|
+
handle.cancel()
|
|
118
|
+
promise.resolve(null)
|
|
119
|
+
}.runOnQueue(Queues.MAIN)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Errors thrown across the JS boundary. Expo Modules API converts a thrown
|
|
125
|
+
* [CodedException] into a JS `Error` carrying `.code` and `.message` — the
|
|
126
|
+
* same shape iOS's `Exception` gives, and the same shape `FlutterError`'s
|
|
127
|
+
* `code`/`message` gave the Flutter side. Unlike iOS's `Exception` (whose
|
|
128
|
+
* JS-visible message reads a separate `reason` property, not the
|
|
129
|
+
* `description` an easy-to-reach initializer sets — see the iOS module's
|
|
130
|
+
* `fix(ios)` commit), `CodedException`'s `message` constructor argument is
|
|
131
|
+
* exactly what reaches JS, so no equivalent subclass trick is needed here.
|
|
132
|
+
*/
|
|
133
|
+
private class ExpoProVideoEditorException(
|
|
134
|
+
code: String,
|
|
135
|
+
message: String,
|
|
136
|
+
cause: Throwable? = null,
|
|
137
|
+
) : CodedException(code, message, cause) {
|
|
138
|
+
companion object {
|
|
139
|
+
fun invalidArguments(message: String) =
|
|
140
|
+
ExpoProVideoEditorException("INVALID_ARGUMENTS", message)
|
|
141
|
+
|
|
142
|
+
fun taskAlreadyRunning(id: String) =
|
|
143
|
+
ExpoProVideoEditorException(
|
|
144
|
+
"TASK_ALREADY_RUNNING", "Task with id $id is already running"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
fun taskNotFound(id: String) =
|
|
148
|
+
ExpoProVideoEditorException("TASK_NOT_FOUND", "No task found for id $id")
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* A pipeline can also cancel itself (a stalled-export watchdog, a refused
|
|
152
|
+
* start), so a bare cancellation-shaped failure is a cancellation even
|
|
153
|
+
* when `canceled` (an explicit `cancelRender` call) is false.
|
|
154
|
+
*/
|
|
155
|
+
fun renderFailed(canceled: Boolean, error: Throwable): ExpoProVideoEditorException {
|
|
156
|
+
val isCancellation = error is java.util.concurrent.CancellationException
|
|
157
|
+
val code = if (canceled || isCancellation) "CANCELED" else "RENDER_ERROR"
|
|
158
|
+
return ExpoProVideoEditorException(
|
|
159
|
+
code, error.message ?: "Unknown render error", error
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|