react-native-photo-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 (162) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/CONTRIBUTING.md +101 -0
  3. package/LICENSE +20 -0
  4. package/PhotoVideoEditor.podspec +23 -0
  5. package/README.md +53 -0
  6. package/android/build.gradle +61 -0
  7. package/android/src/main/AndroidManifest.xml +12 -0
  8. package/android/src/main/java/com/photovideoeditor/PhotoVideoEditorModule.kt +126 -0
  9. package/android/src/main/java/com/photovideoeditor/PhotoVideoEditorPackage.kt +31 -0
  10. package/android/src/main/java/com/photovideoeditor/editor/PhotoVideoEditorActivity.kt +2521 -0
  11. package/android/src/main/java/com/photovideoeditor/editor/README.md +5 -0
  12. package/android/src/main/java/com/photovideoeditor/files/SourceResolver.kt +29 -0
  13. package/android/src/main/java/com/photovideoeditor/media/BitmapSampling.kt +11 -0
  14. package/android/src/main/java/com/photovideoeditor/model/EditorRequest.kt +3 -0
  15. package/android/src/main/java/com/photovideoeditor/photo/export/PhotoExporter.kt +201 -0
  16. package/android/src/main/java/com/photovideoeditor/photo/render/OverlayGeometry.kt +22 -0
  17. package/android/src/main/java/com/photovideoeditor/photo/render/PhotoAdjustmentRenderer.kt +160 -0
  18. package/android/src/main/java/com/photovideoeditor/photo/render/PhotoAdjustments.kt +55 -0
  19. package/android/src/main/java/com/photovideoeditor/photo/render/PhotoEditSession.kt +121 -0
  20. package/android/src/main/java/com/photovideoeditor/photo/render/PhotoFilterPresets.kt +64 -0
  21. package/android/src/main/java/com/photovideoeditor/photo/render/PhotoLayer.kt +54 -0
  22. package/android/src/main/java/com/photovideoeditor/photo/render/PhotoLayerRenderer.kt +108 -0
  23. package/android/src/main/java/com/photovideoeditor/photo/render/PhotoLayerStack.kt +60 -0
  24. package/android/src/main/java/com/photovideoeditor/photo/render/PhotoTransformState.kt +52 -0
  25. package/android/src/main/java/com/photovideoeditor/photo/ui/CropOverlayView.kt +168 -0
  26. package/android/src/main/java/com/photovideoeditor/photo/ui/FilterThumbnailLoader.kt +57 -0
  27. package/android/src/main/java/com/photovideoeditor/photo/ui/LayerOverlayView.kt +202 -0
  28. package/android/src/main/java/com/photovideoeditor/photo/ui/PhotoPreviewRenderCoordinator.kt +57 -0
  29. package/android/src/main/java/com/photovideoeditor/photo/ui/ZoomableImageView.kt +96 -0
  30. package/android/src/main/java/com/photovideoeditor/stickers/OnlineStickerSheet.kt +310 -0
  31. package/android/src/main/java/com/photovideoeditor/stickers/OpenMojiCatalog.kt +172 -0
  32. package/android/src/main/java/com/photovideoeditor/stickers/RemoteImageLoader.kt +110 -0
  33. package/android/src/main/java/com/photovideoeditor/ui/DesignTokens.kt +93 -0
  34. package/android/src/main/java/com/photovideoeditor/ui/components/EditorComponents.kt +119 -0
  35. package/android/src/main/java/com/photovideoeditor/validation/EditorRequestValidator.kt +11 -0
  36. package/android/src/main/java/com/photovideoeditor/video/cache/TimelineThumbnailRepository.kt +130 -0
  37. package/android/src/main/java/com/photovideoeditor/video/export/VideoExporter.kt +319 -0
  38. package/android/src/main/java/com/photovideoeditor/video/render/VideoClip.kt +20 -0
  39. package/android/src/main/java/com/photovideoeditor/video/render/VideoEditSession.kt +136 -0
  40. package/android/src/main/java/com/photovideoeditor/video/render/VideoThumbnailGenerator.kt +31 -0
  41. package/android/src/main/java/com/photovideoeditor/video/render/VideoTransformState.kt +48 -0
  42. package/android/src/main/java/com/photovideoeditor/video/ui/TrimRangeView.kt +104 -0
  43. package/android/src/main/res/drawable/ic_add.xml +9 -0
  44. package/android/src/main/res/drawable/ic_arrow_back.xml +9 -0
  45. package/android/src/main/res/drawable/ic_aspect_ratio.xml +9 -0
  46. package/android/src/main/res/drawable/ic_auto_fix_high.xml +9 -0
  47. package/android/src/main/res/drawable/ic_check.xml +9 -0
  48. package/android/src/main/res/drawable/ic_chevron_left.xml +9 -0
  49. package/android/src/main/res/drawable/ic_chevron_right.xml +9 -0
  50. package/android/src/main/res/drawable/ic_close.xml +9 -0
  51. package/android/src/main/res/drawable/ic_compare.xml +9 -0
  52. package/android/src/main/res/drawable/ic_content_copy.xml +9 -0
  53. package/android/src/main/res/drawable/ic_content_cut.xml +9 -0
  54. package/android/src/main/res/drawable/ic_crop.xml +9 -0
  55. package/android/src/main/res/drawable/ic_crop_square.xml +9 -0
  56. package/android/src/main/res/drawable/ic_delete.xml +9 -0
  57. package/android/src/main/res/drawable/ic_drag_indicator.xml +9 -0
  58. package/android/src/main/res/drawable/ic_face.xml +9 -0
  59. package/android/src/main/res/drawable/ic_filter_center_focus.xml +9 -0
  60. package/android/src/main/res/drawable/ic_flare.xml +9 -0
  61. package/android/src/main/res/drawable/ic_format_size.xml +9 -0
  62. package/android/src/main/res/drawable/ic_fullscreen.xml +9 -0
  63. package/android/src/main/res/drawable/ic_layers.xml +9 -0
  64. package/android/src/main/res/drawable/ic_light_mode.xml +9 -0
  65. package/android/src/main/res/drawable/ic_lock.xml +9 -0
  66. package/android/src/main/res/drawable/ic_magic_button.xml +9 -0
  67. package/android/src/main/res/drawable/ic_movie.xml +9 -0
  68. package/android/src/main/res/drawable/ic_opacity.xml +9 -0
  69. package/android/src/main/res/drawable/ic_palette.xml +9 -0
  70. package/android/src/main/res/drawable/ic_photo_filter.xml +9 -0
  71. package/android/src/main/res/drawable/ic_redo.xml +9 -0
  72. package/android/src/main/res/drawable/ic_rotate_90_degrees_ccw.xml +9 -0
  73. package/android/src/main/res/drawable/ic_rotate_left.xml +9 -0
  74. package/android/src/main/res/drawable/ic_rotate_right.xml +9 -0
  75. package/android/src/main/res/drawable/ic_sentiment_satisfied.xml +9 -0
  76. package/android/src/main/res/drawable/ic_share.xml +9 -0
  77. package/android/src/main/res/drawable/ic_speed.xml +9 -0
  78. package/android/src/main/res/drawable/ic_straighten.xml +9 -0
  79. package/android/src/main/res/drawable/ic_title.xml +9 -0
  80. package/android/src/main/res/drawable/ic_tune.xml +9 -0
  81. package/android/src/main/res/drawable/ic_undo.xml +9 -0
  82. package/android/src/main/res/drawable/ic_visibility_off.xml +9 -0
  83. package/android/src/main/res/drawable/ic_wallpaper.xml +9 -0
  84. package/android/src/main/res/values/styles.xml +10 -0
  85. package/docs/api.md +9 -0
  86. package/docs/architecture.md +9 -0
  87. package/docs/customization.md +18 -0
  88. package/docs/development.md +21 -0
  89. package/docs/formats.md +27 -0
  90. package/docs/performance.md +3 -0
  91. package/docs/permissions.md +3 -0
  92. package/docs/photo-editor.md +41 -0
  93. package/docs/roadmap.md +12 -0
  94. package/docs/technology-decisions.md +17 -0
  95. package/docs/video-editor.md +76 -0
  96. package/ios/Editor/DesignTokens.swift +75 -0
  97. package/ios/Editor/PhotoVideoEditorOutcome.swift +9 -0
  98. package/ios/Editor/PhotoVideoEditorViewController.swift +2064 -0
  99. package/ios/Editor/README.md +5 -0
  100. package/ios/Editor/UI/EditorComponents.swift +138 -0
  101. package/ios/Files/SourceResolver.swift +13 -0
  102. package/ios/Models/EditorRequest.swift +3 -0
  103. package/ios/Photo/Export/PhotoExporter.swift +144 -0
  104. package/ios/Photo/Rendering/OverlayGeometry.swift +22 -0
  105. package/ios/Photo/Rendering/PhotoAdjustmentRenderer.swift +141 -0
  106. package/ios/Photo/Rendering/PhotoAdjustments.swift +57 -0
  107. package/ios/Photo/Rendering/PhotoEditSession.swift +121 -0
  108. package/ios/Photo/Rendering/PhotoFilterPresets.swift +45 -0
  109. package/ios/Photo/Rendering/PhotoLayer.swift +59 -0
  110. package/ios/Photo/Rendering/PhotoLayerRenderer.swift +94 -0
  111. package/ios/Photo/Rendering/PhotoLayerStack.swift +60 -0
  112. package/ios/Photo/Rendering/PhotoTransformState.swift +49 -0
  113. package/ios/Photo/Rendering/PixelBuffer.swift +68 -0
  114. package/ios/Photo/UI/CropOverlayView.swift +161 -0
  115. package/ios/Photo/UI/LayerOverlayView.swift +176 -0
  116. package/ios/Photo/UI/ZoomableImageView.swift +79 -0
  117. package/ios/PhotoVideoEditor.mm +20 -0
  118. package/ios/PhotoVideoEditor.swift +70 -0
  119. package/ios/Stickers/OnlineStickerSheetViewController.swift +418 -0
  120. package/ios/Stickers/OpenMojiCatalog.swift +188 -0
  121. package/ios/Stickers/RemoteImageLoader.swift +127 -0
  122. package/ios/Validation/EditorRequestValidator.swift +9 -0
  123. package/ios/Video/Cache/TimelineThumbnailRepository.swift +148 -0
  124. package/ios/Video/Export/VideoExporter.swift +275 -0
  125. package/ios/Video/Render/VideoClip.swift +21 -0
  126. package/ios/Video/Render/VideoEditSession.swift +99 -0
  127. package/ios/Video/Render/VideoThumbnailGenerator.swift +25 -0
  128. package/ios/Video/Render/VideoTransformState.swift +23 -0
  129. package/ios/Video/UI/TrimRangeView.swift +91 -0
  130. package/lib/module/NativePhotoVideoEditor.js +5 -0
  131. package/lib/module/NativePhotoVideoEditor.js.map +1 -0
  132. package/lib/module/errors.js +29 -0
  133. package/lib/module/errors.js.map +1 -0
  134. package/lib/module/events.js +27 -0
  135. package/lib/module/events.js.map +1 -0
  136. package/lib/module/index.js +91 -0
  137. package/lib/module/index.js.map +1 -0
  138. package/lib/module/package.json +1 -0
  139. package/lib/module/project.js +4 -0
  140. package/lib/module/project.js.map +1 -0
  141. package/lib/module/types.js +2 -0
  142. package/lib/module/types.js.map +1 -0
  143. package/lib/typescript/package.json +1 -0
  144. package/lib/typescript/src/NativePhotoVideoEditor.d.ts +11 -0
  145. package/lib/typescript/src/NativePhotoVideoEditor.d.ts.map +1 -0
  146. package/lib/typescript/src/errors.d.ts +11 -0
  147. package/lib/typescript/src/errors.d.ts.map +1 -0
  148. package/lib/typescript/src/events.d.ts +5 -0
  149. package/lib/typescript/src/events.d.ts.map +1 -0
  150. package/lib/typescript/src/index.d.ts +15 -0
  151. package/lib/typescript/src/index.d.ts.map +1 -0
  152. package/lib/typescript/src/project.d.ts +24 -0
  153. package/lib/typescript/src/project.d.ts.map +1 -0
  154. package/lib/typescript/src/types.d.ts +108 -0
  155. package/lib/typescript/src/types.d.ts.map +1 -0
  156. package/package.json +154 -0
  157. package/src/NativePhotoVideoEditor.ts +12 -0
  158. package/src/errors.ts +78 -0
  159. package/src/events.ts +43 -0
  160. package/src/index.ts +123 -0
  161. package/src/project.ts +17 -0
  162. package/src/types.ts +121 -0
@@ -0,0 +1,12 @@
1
+ # Roadmap
2
+
3
+ 1. Foundation and native contract. **Complete**
4
+ 2. Full-screen native presentation, navigation, and theming. **Complete and Gradle-built for Android (`assembleDebug` passes offline); iOS implemented, not locally built (no macOS/Xcode in this environment)**
5
+ 3. Single-photo crop, rotate, flip, straighten, adjustments (brightness/contrast/saturation/exposure/gamma/temperature/tint/highlights/shadows/sharpen/blur/pixelate/mirror), original preset filters, and JPEG/PNG/WebP export. **Complete and Gradle-built for Android (`assembleDebug` passes offline); iOS implemented with matching architecture and formulas, not locally built (no macOS/Xcode in this environment).**
6
+ 4. Photo layers: text, stickers, shapes, freehand drawing, selection/transform/duplicate/lock/hide/reorder/delete, and undo/redo. **Complete and Gradle-built for Android (`assembleDebug` passes offline); iOS implemented with matching architecture, not locally built.** Font-file registration (`EditorOptions.fonts`) is accepted but not yet wired to load custom font files — text layers only resolve system font names today. See `docs/photo-editor.md` for the full gap list.
7
+ 5. Single-video preview, trim, mute, cover-frame selection, rotate/flip, and H.264/AAC MP4 export. **Complete and Gradle-built for Android against Media3 1.4.1 (resolved live — this environment has network access); iOS implemented via AVFoundation (`AVMutableComposition`/`AVAssetExportSession`), not locally built.** Aspect-ratio crop is Android-only — scoped out on iOS as unverifiable transform math without a device. See `docs/video-editor.md` for the full gap list.
8
+ 6. Video overlays, speed, filters, and adjustments; cancellation, background behavior, drafts, and recovery. **Complete on Android; one iOS gap remaining.** Playback speed (0.5x/1x/1.5x/2x) is implemented for preview on both platforms (export does not yet honor it — documented scope cut, Media3 1.4.1 lacks the needed `SpeedProvider` API). Timed text/sticker/shape overlays are implemented with full selection/transform (Scale/Rotate/Opacity)/time-range editing (Start/End) and undo/redo, reusing the same `LayerOverlayView` built for photo layers, Gradle-built for Android including the Media3 `OverlayEffect` export path (genuinely time-aware, respecting each overlay's range); iOS implements the same editing UI via `AVVideoCompositionCoreAnimationTool` but its export still burns in overlays for the whole clip regardless of range — a custom `AVVideoCompositing` implementation would be needed to fix that, not locally built either way. Video-level Brightness/Contrast/Saturation filters are implemented and Gradle-built for Android (export-only, verified against actual Media3 bytecode); iOS scoped out for the same `AVVideoCompositing` reason. See `docs/video-editor.md` for the full gap list.
9
+ 7. Multi-clip timeline, audio, transitions, and advanced GPU effects. **Multi-clip timeline complete on both platforms (audio tracks and transitions deferred — see below).** A `VideoClip` list (id/source/trim) replaces the single implicit video; a clip strip UI supports Split (at the playhead)/Duplicate/Delete/Move-Left/Move-Right, and the trim range view rebinds to whichever clip is selected. Android is Gradle-built: ExoPlayer now plays a multi-item playlist (`player.setMediaItems`) with a `VideoEditSession.globalPositionMs()`/`seekToGlobalMs()` pair bridging the composed timeline and the player's own per-item position; export concatenates every clip into one `EditedMediaItemSequence`/`Composition`, verified against the actual Media3 1.4.1 bytecode (`javap`) before writing the code — `EditedMediaItemSequence(List<EditedMediaItem>)`, `Composition.Builder(EditedMediaItemSequence, ...)`, and `Transformer.start(Composition, String)` all confirmed to exist. iOS takes a simpler, architecture-appropriate path: both preview and export concatenate every clip's trimmed range into one `AVMutableComposition` via `insertTimeRange(_:of:at:)` (which supports multi-segment concatenation natively), so `player.currentTime()`/`.seek(to:)` already are the composed-timeline position with no separate global-position bookkeeping needed — not locally built (no macOS/Xcode here). Every clip in this pass shares one source file (created via Split/Duplicate); importing separate additional source files from a system picker, audio tracks, and transitions remain deferred to a follow-up slice of this milestone, per the master spec's own guidance to stabilize gapless multi-clip export first.
10
+ 8. Compatibility matrix, benchmarks, and stable 1.0 API.
11
+
12
+ Milestones exclude multi-clip timelines, speculative GPU/FFmpeg dependencies, background exporting, and broad storage permissions until Milestone 7.
@@ -0,0 +1,17 @@
1
+ # Technology decisions
2
+
3
+ The SDK remains an original implementation and does not install or wrap a commercial editor. Android uses Canvas/Bitmap/ColorMatrix for photo work (Milestones 2–4). iOS uses UIKit, Core Graphics, and hand-implemented per-pixel formulas for the same (chosen deliberately over Core Image so both platforms compute identical math and stay visually close, and so the exact filters applied are auditable rather than opaque built-in kernels).
4
+
5
+ ## Video (Milestone 5)
6
+
7
+ | Concern | Android | iOS |
8
+ | --- | --- | --- |
9
+ | Preview playback | Media3 `ExoPlayer` + `PlayerView` | `AVPlayer` + `AVPlayerViewController` |
10
+ | Thumbnail strip | `MediaMetadataRetriever` (background thread) | `AVAssetImageGenerator` (background queue) |
11
+ | Trim/mute/rotate/flip export | Media3 `Transformer` + `EditedMediaItem` + `Effects` | `AVMutableComposition` + `AVMutableVideoComposition` + `AVAssetExportSession` |
12
+ | Aspect-ratio crop | `Presentation.createForAspectRatio` | **Not implemented** — see `docs/video-editor.md` |
13
+ | Output codec | H.264 video / AAC audio, MP4 | H.264 video / AAC audio, MP4 (`AVAssetExportPresetHighestQuality`) |
14
+
15
+ **Why Media3 over FFmpeg or a custom `MediaCodec` pipeline**: Media3 is Apache-2.0, actively maintained by Google, targets Android 7+ (well under this package's `minSdk 24`), and its `Transformer` API is purpose-built for exactly this job (trim, effects, re-encode) without hand-rolling codec plumbing. It resolves live from Google's Maven (`androidx.media3:*:1.4.1`) — this development environment has network access, so this was verified by an actual successful Gradle dependency resolution and compile, not assumed. FFmpeg remains excluded: no LGPL/GPL binary-size or store-compliance review has been done, and Media3 covers Milestone 5's requirements without it.
16
+
17
+ **Why AVFoundation over a custom `AVAssetWriter` pipeline on iOS**: `AVAssetExportSession` is the standard, stable, high-level API for exactly this class of edit (trim + composition + re-encode) and needs far less state-machine code than driving `AVAssetReader`/`AVAssetWriter` directly — lower risk to get right without a device to test on. The tradeoff is less control (e.g., no live progress `Timer`-free callback, hence the 0.3s polling loop, and no straightforward path to combining crop with rotation that was verifiable without a device — see the aspect-ratio gap above).
@@ -0,0 +1,76 @@
1
+ # Video editor
2
+
3
+ ## Milestone 5 — single-video preview, trim, mute, cover frame, and export
4
+
5
+ - **Playback** — a custom player (Media3 `ExoPlayer`/`PlayerView` on Android, `AVPlayer`/`AVPlayerViewController` on iOS) with Play/Pause, a scrubber, and a current-time/duration label, polled every ~250ms (no push-based position API on either platform).
6
+ - **Trim** — a dual-handle range bar below the player, backed by a 12-thumbnail strip generated off the main thread (`MediaMetadataRetriever` / `AVAssetImageGenerator`). Dragging a handle updates the trim range live; a minimum-length gap between handles is enforced.
7
+ - **Mute** — toggles live preview volume and excludes the audio track at export.
8
+ - **Cover frame** — tap **Cover** while scrubbed to the desired frame to record it as `coverFrameMs`. The frame itself is not yet extracted to an image file — the timestamp is tracked but not returned on `EditorResult` yet (a small follow-up).
9
+ - **Rotate** — cycles 90° increments, applied at export via a rotation transform; the preview shows a live `rotation`/`transform` approximation on the player view rather than a full re-render.
10
+ - **Flip** — horizontal flip, same live-approximation preview treatment as rotate.
11
+ - **Aspect-ratio crop** (Original/1:1/4:5/9:16/16:9) — **Android only.** On iOS this is scoped out for now: combining a crop with the source's own orientation-correcting transform and an additional rotation inside `AVMutableVideoCompositionLayerInstruction`'s coordinate space is easy to get subtly wrong, and there is no device available in this environment to verify the pixel math, so it was not shipped unverified. Selecting a preset on iOS shows a toast noting the gap rather than silently doing nothing.
12
+ - **Export** — H.264 video / AAC audio MP4, via Media3 `Transformer` (Android) or `AVAssetExportSession` with `AVMutableComposition`/`AVMutableVideoComposition` (iOS). Progress is polled (~300ms) and shown as an indeterminate spinner with a **Cancel export** button; cancellation deletes the partial output file. `EditorResult` returns real `width`/`height`/`duration`/`fileSize`/`mimeType`.
13
+
14
+ ## Milestone 6 — playback speed, timed overlays, and overlay editing
15
+
16
+ - **Speed** — cycles 0.5x → 1x → 1.5x → 2x → 0.5x…, applied live to the preview player (`ExoPlayer.setPlaybackSpeed` / `AVPlayer.rate`) and shown on the toolbar button itself (e.g. "Speed 1.5x") rather than a highlight, since it's a cycling value, not an on/off mode.
17
+ - **Export does not yet honor speed** — this is a deliberate, documented scope cut, not an oversight. Changing output speed correctly (video timestamp remapping + audio pitch-preserving time-stretch) is a meaningfully different problem from trim/mute/rotate/flip, with real risk of getting the Media3 `SpeedProvider`/`TimestampAdjustment` or AVFoundation `scaleTimeRange` math wrong without a device to verify against. Exporting after setting a non-1x speed currently produces a normal-speed file — the UI does not yet warn about this mismatch (a follow-up).
18
+ - **Text/Sticker/Shape overlays** — tap **Text** or **Stickers** on the video toolbar (the sticker picker also offers Rectangle/Oval/Line shapes) to add an overlay, reusing the exact same `PhotoLayer`/`PhotoLayerStack`/`PhotoLayerRenderer` machinery built for photo layers (Milestone 4) rather than a parallel system. `PhotoLayer` gained `startMs`/`endMs` fields for time-ranging, with an `isActiveAt`/`isActive(atMs:)` helper.
19
+ - Preview: active overlays (those whose time range contains the current playhead) are rendered onto a transparent image placed over the player, refreshed on the same ~250ms position-poll tick already used for the scrubber.
20
+ - Export: burned in via Media3 `OverlayEffect`/`BitmapOverlay` (Android) or `AVVideoCompositionCoreAnimationTool` (iOS).
21
+ - Header Undo/Redo now works for video overlays too, same 50-entry snapshot history as photo layers.
22
+ - **Overlay selection, transform, and time-range editing** — tap an overlay on the video preview to select it (reuses the same `LayerOverlayView` built for photo layers: dashed bounding box, tap-to-select, drag-to-move). Once selected, a mini toolbar offers Scale/Rotate/Opacity (shared slider, same pattern as photo) plus **Start**/**End** chips that scrub the overlay's time range against the video's duration, and Delete.
23
+ - Android export now genuinely respects per-overlay time ranges: `BitmapOverlay.getBitmap(presentationTimeUs:)` recomputes the active layer set per callback (cached by an active-layer-ID signature so it only re-renders when that set actually changes, not on every frame). **One unverified detail**: this assumes Transformer's effect callbacks receive presentation timestamps rebased to the trimmed output's own zero point, so `trimStartMs` is added back before comparing against `startMs`/`endMs` (which are authored against the untrimmed source's timeline) — there's no device here to confirm this assumption.
24
+ - **iOS export does not yet respect time ranges** — every overlay is still burned in for the whole clip regardless of its configured range, even though the editor UI lets you set one. `AVVideoCompositionCoreAnimationTool` only composites a fixed `CALayer` tree; making it time-varying like Android's per-frame callback needs a custom `AVVideoCompositing` implementation, a distinctly bigger change than anything else in this pass. This is the one remaining gap in Milestone 6.
25
+ - **Video-level filters** (Brightness/Contrast/Saturation) — **Android only, export-only (no live preview)**. Verified against the actual Media3 1.4.1 bytecode (via `javap`, not guessed) before writing any code: `androidx.media3.effect.Brightness(Float)`, `Contrast(Float)`, and `HslAdjustment.Builder().adjustSaturation(Float)` all exist and implement `Effect` in this exact version. iOS is scoped out: applying per-frame color filters requires `AVMutableVideoComposition(asset:applyingCIFiltersWithHandler:)`, which creates its own composition and conflicts architecturally with the manual `AVVideoCompositionLayerInstruction`-based composition already used here for rotate/flip/crop — combining them needs the same custom `AVVideoCompositing` implementation noted above. Tapping Filters on iOS video shows a "coming in a later milestone" message.
26
+ - A real API-version lesson from this pass: an attempt to also apply `speed` at export via `SpeedProvider`/`TimestampAdjustment(SpeedProvider)` found that API doesn't exist in Media3 1.4.1. Bumping the dependency to 1.9.4 to get it broke three other already-verified call sites (`Transformer.Builder.setTransformationRequest`, `OverlayEffect`'s constructor list type, and `SpeedProvider` still wasn't found even there). The version bump was reverted rather than chased further mid-session — `speed` stays preview-only, documented above, and 1.4.1 remains pinned since everything else against it is verified working.
27
+
28
+ **Still stubs**: Filters on video (iOS only — Android has it, see above).
29
+
30
+ ### Known gaps
31
+
32
+ - iOS aspect-ratio crop is not implemented (see above) — Android has it.
33
+ - iOS video-level filters (Brightness/Contrast/Saturation) are not implemented (see above) — Android has it, export-only.
34
+ - **iOS video overlay export does not respect per-overlay time ranges** (see above) — the editor UI supports setting them on both platforms, but only Android's export honors them. This is the one remaining Milestone 6 gap.
35
+ - Cover-frame image extraction (saving the selected frame as a JPEG and returning its URI) is not implemented yet; only the timestamp is tracked internally.
36
+ - Playback speed is preview-only on both platforms; export ignores it (see above for why).
37
+ - Export progress is a spinner, not wired to the JS `addExportProgressListener` event bridge yet — consistent with how photo export currently works, but a real gap for a later milestone.
38
+ - No variable-frame-rate handling beyond what each platform's default export pipeline does automatically.
39
+ - `cancelExport()` (the public JS API) is still a no-op stub — cancellation only works via the in-editor Cancel button while the exporter is running inside the native UI.
40
+
41
+ **Verification status**: Android is compiled and built via Gradle (`:react-native-photo-video-editor:assembleDebug`, `:app:assembleDebug`) against Media3 1.4.1, resolved live from Google's Maven (this environment has network access, unlike the fully-offline assumption of earlier milestones), including the `OverlayEffect`/`BitmapOverlay` overlay export path (now time-aware) and the `Brightness`/`Contrast`/`HslAdjustment` filter export path. It has not been exercised on an emulator/device with a real video file — in particular, the presentation-timestamp-rebasing assumption in the overlay time-range logic is unverified. iOS mirrors the overlay selection/transform/time-range UI (minus time-range-aware export, aspect crop, and video-level filters) using AVFoundation but is unverified — no macOS/Xcode toolchain is available here.
42
+
43
+ ## Milestone 7 (partial) — multi-clip timeline
44
+
45
+ The single implicit video is replaced by an ordered `VideoClip` list (`id`, `sourceUri`, `originalDurationMs`, `trimStartMs`/`trimEndMs`). Every clip in this pass shares one source file — new clips only come from **Split** and **Duplicate**, not from importing separate additional source files (a follow-up). Audio tracks and transitions, the other two pieces the master spec bundles into Milestone 7, are **deliberately deferred** — per the spec's own guidance to add transitions only after gapless multi-clip export is stable.
46
+
47
+ - **Clip strip** — a horizontal strip below the trim range view lists every clip as a chip (`Clip N` + trimmed duration); tapping one selects it, seeks the player to its start, and rebinds the trim range view to that clip's own `originalDurationMs` (previously the trim view was bound to the whole video).
48
+ - **Split** — cuts the selected clip into two at the current playhead position, both sharing the original source file and together covering the exact same trimmed range as before the split.
49
+ - **Duplicate** — inserts a copy of the selected clip immediately after itself (new `id`, same source/trim).
50
+ - **Delete** — removes the selected clip; refuses to leave the timeline with zero clips.
51
+ - **Move Left / Move Right** — reorders the selected clip by one position.
52
+ - **Trim** — now edits whichever clip is selected (fractions relative to that clip's own `originalDurationMs`), not the whole video.
53
+
54
+ ### Android
55
+
56
+ ExoPlayer plays a genuine multi-item playlist (`player.setMediaItems(List<MediaItem>)`, one `MediaItem` per clip with its own `ClippingConfiguration`). Because ExoPlayer's own `currentPosition`/`seekTo` are per-item, `VideoEditSession` adds `globalPositionMs()` (preceding clips' trimmed durations + position in the current item) and `seekToGlobalMs(Long)` (the inverse) so the rest of the UI — scrubber, time label, cover-frame, overlay-preview timing — can keep working against one composed timeline exactly as before. `coverFrameMs` and overlay `startMs`/`endMs` are now authored against this global timeline.
57
+
58
+ Export concatenates every clip into a single `EditedMediaItemSequence` passed to `Composition`, replacing the old single-`EditedMediaItem` call. Verified against the actual Media3 1.4.1 bytecode (`javap` on the real `media3-transformer-1.4.1.aar`, not guessed) before writing the code:
59
+ - `EditedMediaItemSequence(EditedMediaItem, EditedMediaItem...)` and `EditedMediaItemSequence(List<EditedMediaItem>)` both exist.
60
+ - `Composition.Builder(EditedMediaItemSequence, EditedMediaItemSequence...)` and `Composition.Builder(List<EditedMediaItemSequence>)` both exist.
61
+ - `Transformer.start(Composition, String)` exists alongside the previously-used `start(EditedMediaItem, String)`.
62
+
63
+ Each clip becomes its own `EditedMediaItem` with the same global rotate/flip/aspect/filter effects; the per-overlay time-range `BitmapOverlay` callback now offsets its own item-local `presentationTimeUs` by the summed trimmed duration of the preceding clips to recover the global position that overlay `startMs`/`endMs` are authored against — the same unverified-without-a-device rebasing assumption carried over from Milestone 6, just generalized to per-clip offsets instead of one global `trimStartMs`.
64
+
65
+ Gradle-built: `:react-native-photo-video-editor:compileDebugKotlin`, `:react-native-photo-video-editor:assembleDebug`, and `:app:assembleDebug` all pass. Not exercised on an emulator/device with a real multi-clip export.
66
+
67
+ ### iOS
68
+
69
+ Takes an architecturally simpler path than Android: rather than a multi-item player, **both preview and export concatenate every clip's trimmed range into a single `AVMutableComposition`** via `insertTimeRange(_:of:at:)`, which supports multi-segment concatenation natively. This means `player.currentTime()`/`.seek(to:)` already **are** the composed-timeline position — there is no separate global-vs-per-item position to reconcile, unlike Android's playlist approach, so no `globalPositionMs()`/`seekToGlobalMs()` equivalent was needed. This is a deliberate, documented platform difference, not an inconsistency. `VideoEditSession.asset(for:)` resolves any one clip's own `AVURLAsset` (used for natural-size lookups and per-clip thumbnail generation). Not locally built — no macOS/Xcode toolchain here.
70
+
71
+ ### Known gaps (this slice)
72
+
73
+ - All clips share one source file — no system file picker to import additional separate videos yet.
74
+ - Audio tracks (adding/mixing separate audio) — deferred, not started.
75
+ - Transitions between clips — deferred, not started; the master spec recommends only adding these once gapless export is stable, which this slice establishes.
76
+ - Android's per-clip overlay-offset rebasing and iOS's whole-timeline composition are both unverified on-device, same caveat as every other Transformer/AVFoundation timing assumption in this project so far.
@@ -0,0 +1,75 @@
1
+ import UIKit
2
+
3
+ /// Default values for the "Studio Violet" design system used by the native editor
4
+ /// shell, matching the Stitch-exported mockups under
5
+ /// stitch_react_native_photo_editor_sdk/studio_violet_creative_sdk/DESIGN.md.
6
+ /// Mirrors `DesignTokens.kt`. These are FALLBACKS only — every color a consumer
7
+ /// can already override via `EditorOptions.theme` still wins; this object just
8
+ /// changes what the editor looks like when no override is given.
9
+ enum DesignTokens {
10
+ // Surface hierarchy
11
+ static let surfaceDim = UIColor(red: 0x13 / 255, green: 0x13 / 255, blue: 0x16 / 255, alpha: 1)
12
+ static let surface = surfaceDim
13
+ static let surfaceContainerLowest = UIColor(red: 0x0E / 255, green: 0x0E / 255, blue: 0x11 / 255, alpha: 1)
14
+ static let surfaceContainerLow = UIColor(red: 0x1B / 255, green: 0x1B / 255, blue: 0x1E / 255, alpha: 1)
15
+ static let surfaceContainer = UIColor(red: 0x1F / 255, green: 0x1F / 255, blue: 0x22 / 255, alpha: 1)
16
+ static let surfaceContainerHigh = UIColor(red: 0x2A / 255, green: 0x2A / 255, blue: 0x2D / 255, alpha: 1)
17
+ static let surfaceContainerHighest = UIColor(red: 0x35 / 255, green: 0x34 / 255, blue: 0x38 / 255, alpha: 1)
18
+ static let surfaceVariant = surfaceContainerHighest
19
+ static let surfaceBright = UIColor(red: 0x39 / 255, green: 0x39 / 255, blue: 0x3C / 255, alpha: 1)
20
+
21
+ // Content
22
+ static let onSurface = UIColor(red: 0xE4 / 255, green: 0xE1 / 255, blue: 0xE6 / 255, alpha: 1)
23
+ static let onSurfaceVariant = UIColor(red: 0xCC / 255, green: 0xC3 / 255, blue: 0xD8 / 255, alpha: 1)
24
+ static let outline = UIColor(red: 0x95 / 255, green: 0x8D / 255, blue: 0xA1 / 255, alpha: 1)
25
+ static let outlineVariant = UIColor(red: 0x4A / 255, green: 0x44 / 255, blue: 0x55 / 255, alpha: 1)
26
+
27
+ // Accents
28
+ static let primary = UIColor(red: 0xD2 / 255, green: 0xBB / 255, blue: 0xFF / 255, alpha: 1)
29
+ static let onPrimary = UIColor(red: 0x3F / 255, green: 0x00 / 255, blue: 0x8E / 255, alpha: 1)
30
+ static let primaryContainer = UIColor(red: 0x7C / 255, green: 0x3A / 255, blue: 0xED / 255, alpha: 1)
31
+ static let onPrimaryContainer = UIColor(red: 0xED / 255, green: 0xE0 / 255, blue: 0xFF / 255, alpha: 1)
32
+ static let secondary = UIColor(red: 0xC0 / 255, green: 0xC1 / 255, blue: 0xFF / 255, alpha: 1)
33
+ static let tertiary = UIColor(red: 0xD0 / 255, green: 0xBC / 255, blue: 0xFF / 255, alpha: 1)
34
+
35
+ // Literal Stitch accent hexes (DESIGN.md "Primary Accents & Active States"), used by the
36
+ // new EditorUI components where the exact mockup hue matters more than the M3 mapping.
37
+ static let accentViolet = UIColor(red: 0x7C / 255, green: 0x3A / 255, blue: 0xED / 255, alpha: 1)
38
+ static let accentIndigo = UIColor(red: 0x63 / 255, green: 0x66 / 255, blue: 0xF1 / 255, alpha: 1)
39
+ static let accentHighlightViolet = UIColor(red: 0x8B / 255, green: 0x5C / 255, blue: 0xF6 / 255, alpha: 1)
40
+
41
+ // Literal Stitch surface hexes (DESIGN.md "Canvas & Surface Hierarchy").
42
+ static let canvasViewport = UIColor(red: 0x09 / 255, green: 0x09 / 255, blue: 0x0B / 255, alpha: 1)
43
+ static let editorSurface = UIColor(red: 0x12 / 255, green: 0x12 / 255, blue: 0x14 / 255, alpha: 1)
44
+ static let elevatedPanel = UIColor(red: 0x18 / 255, green: 0x18 / 255, blue: 0x1B / 255, alpha: 1)
45
+ static let interactiveNeutral = UIColor(red: 0x27 / 255, green: 0x27 / 255, blue: 0x2A / 255, alpha: 1)
46
+ static let mutedBorderColor = UIColor(red: 0x3F / 255, green: 0x3F / 255, blue: 0x46 / 255, alpha: 1)
47
+
48
+ // Literal Stitch text hexes (DESIGN.md "Typography & Content Grayscale").
49
+ static let textPrimary = UIColor.white
50
+ static let textSecondary = UIColor(red: 0xA1 / 255, green: 0xA1 / 255, blue: 0xAA / 255, alpha: 1)
51
+ static let textSubtle = UIColor(red: 0x71 / 255, green: 0x71 / 255, blue: 0x7A / 255, alpha: 1)
52
+
53
+ // Spacing (points), matching DESIGN.md's `space-*` scale.
54
+ static let spaceXs: CGFloat = 4
55
+ static let spaceSm: CGFloat = 8
56
+ static let spaceMd: CGFloat = 12
57
+ static let spaceLg: CGFloat = 16
58
+ static let spaceXl: CGFloat = 20
59
+ static let space2xl: CGFloat = 24
60
+ static let space3xl: CGFloat = 32
61
+ static let touchTargetMin: CGFloat = 44
62
+
63
+ // Corner radii (points)
64
+ static let radiusSm: CGFloat = 4
65
+ static let radiusMd: CGFloat = 8
66
+ static let radiusLg: CGFloat = 12
67
+ static let radiusXl: CGFloat = 16
68
+ static let radiusSheet: CGFloat = 24
69
+
70
+ // Elevation tiers (DESIGN.md "Elevation & Depth"): background color + opacity to composite
71
+ // persistent chrome / floating trays / popovers over the canvas.
72
+ enum ElevationTier1 { static let color = editorSurface; static let opacity: CGFloat = 0.92 }
73
+ enum ElevationTier2 { static let color = elevatedPanel; static let opacity: CGFloat = 0.88 }
74
+ enum ElevationTier3 { static let color = interactiveNeutral; static let opacity: CGFloat = 1.0 }
75
+ }
@@ -0,0 +1,9 @@
1
+ import Foundation
2
+
3
+ /// What the native editor produced when it finished. `.success` metadata is
4
+ /// populated for photo and video exports alike; `durationMs` is video-only.
5
+ enum PhotoVideoEditorOutcome {
6
+ case cancelled
7
+ case success(uri: String, mimeType: String?, width: Int?, height: Int?, fileSize: Int?, durationMs: Int64? = nil)
8
+ case failure(code: String, message: String)
9
+ }