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.
Files changed (125) hide show
  1. package/.prettierrc +8 -0
  2. package/LICENSE +67 -0
  3. package/NOTICE.md +250 -0
  4. package/README.md +70 -0
  5. package/android/build.gradle +35 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/expo/modules/provideoeditor/ExpoProVideoEditorModule.kt +163 -0
  8. package/android/src/main/java/expo/modules/provideoeditor/src/core/constants/LoggingConstants.kt +4 -0
  9. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/EffectsProcessor.kt +66 -0
  10. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/RenderVideo.kt +1089 -0
  11. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyAnimation.kt +290 -0
  12. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyBitrate.kt +110 -0
  13. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyBlur.kt +27 -0
  14. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyChromaKey.kt +58 -0
  15. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyClipTransition.kt +88 -0
  16. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyColorMatrix.kt +144 -0
  17. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyComposition.kt +86 -0
  18. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyCrop.kt +104 -0
  19. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyFlip.kt +27 -0
  20. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyFrameRate.kt +28 -0
  21. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyImageLayer.kt +397 -0
  22. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyOpacity.kt +24 -0
  23. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyPlaybackSpeed.kt +35 -0
  24. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyRotation.kt +26 -0
  25. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ApplyScale.kt +29 -0
  26. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/AudioPreRenderer.kt +492 -0
  27. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/AudioSequenceBuilder.kt +144 -0
  28. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/BitrateCapPolicy.kt +45 -0
  29. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ChromaKeyEffect.kt +321 -0
  30. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ChromaKeyMath.kt +90 -0
  31. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ClipTransitionGeometry.kt +160 -0
  32. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ClipTransitionRenderer.kt +1002 -0
  33. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/CompositionBuilder.kt +166 -0
  34. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ConfigurableInAppMp4Muxer.kt +137 -0
  35. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/EncoderFailureClassifier.kt +71 -0
  36. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/GifDecoder.kt +76 -0
  37. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/LayeredCompositionBuilder.kt +556 -0
  38. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/MediaInfoExtractor.kt +364 -0
  39. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/ResilientVideoEncoderFactory.kt +307 -0
  40. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoCompositionTransformation.kt +192 -0
  41. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoEncoderConfig.kt +179 -0
  42. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoGlobalTrimCalculator.kt +147 -0
  43. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoReverser.kt +1044 -0
  44. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoSequenceBuilder.kt +905 -0
  45. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoTimelineDurationCalculator.kt +19 -0
  46. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VideoTranscoder.kt +242 -0
  47. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VolumeAudioProcessor.kt +82 -0
  48. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/helpers/VolumeControlAudioMixer.kt +182 -0
  49. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/models/CodecResourceExhaustedException.kt +24 -0
  50. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/models/RenderConfig.kt +572 -0
  51. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/models/RenderJobHandle.kt +45 -0
  52. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/models/VideoEncoderConfigurationException.kt +20 -0
  53. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/utils/RotatedVideoDimensions.kt +68 -0
  54. package/android/src/main/java/expo/modules/provideoeditor/src/features/render/utils/VideoMimeUtils.kt +36 -0
  55. package/android/src/main/java/expo/modules/provideoeditor/src/shared/concurrency/ExportGate.kt +102 -0
  56. package/android/src/main/java/expo/modules/provideoeditor/src/shared/logging/PluginLog.kt +112 -0
  57. package/android/src/main/java/expo/modules/provideoeditor/src/shared/media/EncodedImage.kt +101 -0
  58. package/android/src/main/java/expo/modules/provideoeditor/src/shared/media/ImageOrientation.kt +265 -0
  59. package/android/src/main/java/expo/modules/provideoeditor/src/shared/media/PcmRangeDecoder.kt +239 -0
  60. package/build/ExpoProVideoEditor.types.d.ts +125 -0
  61. package/build/ExpoProVideoEditor.types.d.ts.map +1 -0
  62. package/build/ExpoProVideoEditor.types.js +8 -0
  63. package/build/ExpoProVideoEditor.types.js.map +1 -0
  64. package/build/ExpoProVideoEditorModule.d.ts +20 -0
  65. package/build/ExpoProVideoEditorModule.d.ts.map +1 -0
  66. package/build/ExpoProVideoEditorModule.js +3 -0
  67. package/build/ExpoProVideoEditorModule.js.map +1 -0
  68. package/build/ExpoProVideoEditorModule.web.d.ts +9 -0
  69. package/build/ExpoProVideoEditorModule.web.d.ts.map +1 -0
  70. package/build/ExpoProVideoEditorModule.web.js +16 -0
  71. package/build/ExpoProVideoEditorModule.web.js.map +1 -0
  72. package/build/index.d.ts +3 -0
  73. package/build/index.d.ts.map +1 -0
  74. package/build/index.js +5 -0
  75. package/build/index.js.map +1 -0
  76. package/bun.lock +2132 -0
  77. package/eslint.config.cjs +5 -0
  78. package/expo-module.config.json +9 -0
  79. package/ios/ExpoProVideoEditor.podspec +24 -0
  80. package/ios/ExpoProVideoEditorModule.swift +131 -0
  81. package/ios/src/features/render/RenderVideo.swift +1087 -0
  82. package/ios/src/features/render/helpers/ApplyAnimation.swift +198 -0
  83. package/ios/src/features/render/helpers/ApplyBitrate.swift +112 -0
  84. package/ios/src/features/render/helpers/ApplyBlur.swift +23 -0
  85. package/ios/src/features/render/helpers/ApplyChromaKey.swift +175 -0
  86. package/ios/src/features/render/helpers/ApplyColorMatrix.swift +100 -0
  87. package/ios/src/features/render/helpers/ApplyComposition.swift +45 -0
  88. package/ios/src/features/render/helpers/ApplyCrop.swift +53 -0
  89. package/ios/src/features/render/helpers/ApplyFlip.swift +24 -0
  90. package/ios/src/features/render/helpers/ApplyImageLayer.swift +65 -0
  91. package/ios/src/features/render/helpers/ApplyPlaybackSpeed.swift +57 -0
  92. package/ios/src/features/render/helpers/ApplyRotation.swift +32 -0
  93. package/ios/src/features/render/helpers/ApplyScale.swift +30 -0
  94. package/ios/src/features/render/helpers/AudioPreRenderer.swift +352 -0
  95. package/ios/src/features/render/helpers/AudioReverser.swift +308 -0
  96. package/ios/src/features/render/helpers/AudioSequenceBuilder.swift +162 -0
  97. package/ios/src/features/render/helpers/BitrateCapPolicy.swift +77 -0
  98. package/ios/src/features/render/helpers/BitrateCappedExporter.swift +501 -0
  99. package/ios/src/features/render/helpers/ClipTransitionGeometry.swift +111 -0
  100. package/ios/src/features/render/helpers/ClipTransitionRenderer.swift +397 -0
  101. package/ios/src/features/render/helpers/CompositionBuilder.swift +464 -0
  102. package/ios/src/features/render/helpers/DecodeOrientedImage.swift +60 -0
  103. package/ios/src/features/render/helpers/LayeredCompositionBuilder.swift +371 -0
  104. package/ios/src/features/render/helpers/MediaInfoExtractor.swift +381 -0
  105. package/ios/src/features/render/helpers/TrackEndTrimmer.swift +112 -0
  106. package/ios/src/features/render/helpers/VideoSequenceBuilder.swift +555 -0
  107. package/ios/src/features/render/helpers/VideoTranscoder.swift +283 -0
  108. package/ios/src/features/render/models/RenderConfig.swift +586 -0
  109. package/ios/src/features/render/models/VideoClip.swift +81 -0
  110. package/ios/src/features/render/models/VideoCompositorConfig.swift +78 -0
  111. package/ios/src/features/render/utils/VideoCompositor.swift +1014 -0
  112. package/ios/src/features/render/utils/VideoMimeUtils.swift +24 -0
  113. package/ios/src/shared/EncodedImage.swift +75 -0
  114. package/ios/src/shared/ExportGate.swift +79 -0
  115. package/ios/src/shared/ExportSessionDriver.swift +185 -0
  116. package/ios/src/shared/ExportSessionGuard.swift +85 -0
  117. package/ios/src/shared/ExportWatchdog.swift +198 -0
  118. package/ios/src/shared/LoggingConstants.swift +7 -0
  119. package/ios/src/shared/PluginLog.swift +107 -0
  120. package/package.json +59 -0
  121. package/src/ExpoProVideoEditor.types.ts +148 -0
  122. package/src/ExpoProVideoEditorModule.ts +22 -0
  123. package/src/ExpoProVideoEditorModule.web.ts +22 -0
  124. package/src/index.ts +4 -0
  125. package/tsconfig.json +28 -0
package/.prettierrc ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "printWidth": 100,
3
+ "tabWidth": 2,
4
+ "singleQuote": true,
5
+ "bracketSameLine": true,
6
+ "trailingComma": "es5",
7
+ "jsxSingleQuote": false,
8
+ }
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,2 @@
1
+ <manifest>
2
+ </manifest>
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ package expo.modules.provideoeditor.src.core.constants
2
+
3
+ const val PACKAGE_TAG = "ExpoProVideoEditor"
4
+ const val RENDER_TAG = "$PACKAGE_TAG-Renderer"