react-native-photo-video-editor 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.1
6
+
7
+ - Reduce Android large-video memory pressure with a heap-aware preview sample-buffer target (up to 16 MiB), size-first buffering, and release of preview samples/decoders during export. Restore the paused preview after export cancellation or failure.
8
+
9
+ ## 0.3.0
10
+
11
+ - Breaking API change: migrate `initialStickerId: 'brand'` to `initialStickerIds: ['brand']`.
12
+ - Replace `initialStickerId` with `initialStickerIds` and add a top-right control on the selected default sticker to cycle through default stickers on Android and iOS. Swaps preserve transforms and support undo/redo; the button is enabled only for multiple IDs.
13
+ - Place the swap control on the sticker’s top-right corner, above the scale control, so it follows dragging and rotation.
14
+ - Remove dummy sticker data from the example; default stickers are supplied by the caller.
15
+
5
16
  ## 0.2.2
6
17
 
7
18
  - Add `initialStickerId` to place a matching `stickerAssets` image at the center when opening the photo or video editor on Android and iOS.
package/README.md CHANGED
@@ -57,18 +57,28 @@ See [API](docs/api.md), [architecture](docs/architecture.md), [photo editor](doc
57
57
  For local work, run `yarn`, then `yarn typecheck`, `yarn lint`, `yarn test`, and `yarn prepare`. See [CONTRIBUTING.md](CONTRIBUTING.md). MIT licensed; see [LICENSE](LICENSE).
58
58
 
59
59
  To place a sticker at the center automatically when opening either editor, pass
60
- `initialStickerId` matching exactly one entry in `stickerAssets`:
60
+ `initialStickerIds` containing unique IDs from `stickerAssets`:
61
61
 
62
62
  ```ts
63
63
  await openPhotoEditor({
64
64
  source: { uri: photoUri, type: 'photo' },
65
- stickerAssets: [{ id: 'brand', uri: 'https://example.com/brand-sticker.png' }],
66
- initialStickerId: 'brand',
65
+ stickerAssets: [
66
+ { id: 'brand', uri: 'https://example.com/brand-sticker.png' },
67
+ { id: 'alternate', uri: 'https://example.com/alternate.png' },
68
+ ],
69
+ initialStickerIds: ['brand', 'alternate'],
67
70
  });
68
71
  ```
69
72
 
70
- The sticker starts at the center of the media and can be moved, resized, or
71
- removed. The same option works with `openVideoEditor` and `openEditor`, even if
73
+ The first sticker starts at the center of the media and can be moved, resized, or
74
+ removed. The “Switch default sticker” control at the selected default sticker’s top-right
75
+ corner, above its bottom-right scale control, cycles through the IDs in
76
+ order and wraps around, replacing the current default sticker while preserving
77
+ its transforms. Switching is undoable. After deletion, use Undo to restore the sticker and its
78
+ controls. The swap control is disabled for one ID and appears only on the selected
79
+ default sticker. Failed swaps leave the existing sticker in place. The same option works with `openVideoEditor` and `openEditor`, even if
72
80
  `features.stickers` hides the sticker picker. Omit it to start without a sticker.
73
81
  An unknown or duplicate ID rejects with `E_INVALID_OPTIONS`; an unreadable image
74
82
  rejects with `E_SOURCE_UNREADABLE`. HTTPS and local sticker URIs are supported.
83
+
84
+ Migration: replace `initialStickerId: 'brand'` with `initialStickerIds: ['brand']`.
@@ -188,13 +188,32 @@ class PhotoVideoEditorActivity : Activity() {
188
188
  if (!isFinishing) insertInitialSticker()
189
189
  }
190
190
 
191
- private fun insertInitialSticker() {
192
- val id = request.optString("initialStickerId").takeIf { it.isNotBlank() } ?: return
191
+ private val initialStickerIds: List<String> by lazy {
192
+ val ids = request.optJSONArray("initialStickerIds")
193
+ if (ids == null) emptyList() else (0 until ids.length()).map { ids.optString(it) }.filter { it.isNotBlank() }.distinct()
194
+ }
195
+ private val initialStickerLayerId = java.util.UUID.randomUUID().toString()
196
+ private val initialStickerPaths = mutableMapOf<String, String>()
197
+ private var initialStickerLoading = false
198
+
199
+ private fun currentInitialSticker(): PhotoLayer? =
200
+ (photoSession?.layerStack?.layers ?: videoSession?.layerStack?.layers)?.firstOrNull { it.id == initialStickerLayerId }
201
+
202
+ private fun switchInitialSticker() {
203
+ if (initialStickerLoading || initialStickerIds.size < 2) return
204
+ val currentIndex = initialStickerIds.indexOf(currentInitialSticker()?.stickerId)
205
+ insertInitialSticker((currentIndex + 1) % initialStickerIds.size)
206
+ }
207
+
208
+ private fun insertInitialSticker(index: Int = 0) {
209
+ if (initialStickerLoading) return
210
+ val id = initialStickerIds.getOrNull(index) ?: return
193
211
  val asset = runtimeStickerAssets().firstOrNull { it.id == id } ?: return
212
+ initialStickerLoading = true
194
213
  window.setFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
195
214
  Thread {
196
215
  val path = try {
197
- val localPath = if (asset.uri.startsWith("https://", ignoreCase = true)) {
216
+ val localPath = initialStickerPaths[id] ?: if (asset.uri.startsWith("https://", ignoreCase = true)) {
198
217
  val output = File.createTempFile("pve_initial_sticker_", ".img", cacheDir)
199
218
  val connection = java.net.URL(asset.uri).openConnection().apply {
200
219
  connectTimeout = 10_000
@@ -210,20 +229,31 @@ class PhotoVideoEditorActivity : Activity() {
210
229
  }
211
230
  } catch (_: Exception) { null }
212
231
  runOnUiThread {
232
+ initialStickerLoading = false
213
233
  window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
214
234
  if (isFinishing || isDestroyed) return@runOnUiThread
215
235
  if (path == null) {
216
- finishWithError("The initial sticker could not be loaded.", "E_SOURCE_UNREADABLE")
236
+ if (initialStickerPaths.isEmpty()) finishWithError("The initial sticker could not be loaded.", "E_SOURCE_UNREADABLE")
237
+ else Toast.makeText(this, "Could not load this sticker. Try again.", Toast.LENGTH_SHORT).show()
217
238
  return@runOnUiThread
218
239
  }
240
+ initialStickerPaths[id] = path
219
241
  window.decorView.post {
220
242
  if (isFinishing || isDestroyed) return@post
221
- val layer = newSticker(stickerId = id, stickerUri = Uri.fromFile(File(path)).toString())
243
+ val uri = Uri.fromFile(File(path)).toString()
244
+ val fresh = newSticker(stickerId = id, stickerUri = uri)
245
+ val layer = currentInitialSticker()?.copy(stickerId = id, stickerUri = uri, overlayAspectRatio = fresh.overlayAspectRatio)
246
+ ?: fresh.copy(id = initialStickerLayerId)
247
+ val replace: (List<PhotoLayer>) -> List<PhotoLayer> = { layers ->
248
+ if (layers.any { it.id == initialStickerLayerId }) layers.map { if (it.id == initialStickerLayerId) layer else it }
249
+ else layers + layer
250
+ }
222
251
  if (mediaType == "photo") {
223
- photoSession?.layerStack?.commit { it + layer }
252
+ photoSession?.layerStack?.commit(replace)
253
+ onLayerStackChanged()
224
254
  selectLayer(layer.id)
225
255
  } else videoSession?.let { session ->
226
- session.layerStack.commit { it + layer }
256
+ session.layerStack.commit(replace)
227
257
  onVideoLayerStackChanged()
228
258
  selectVideoLayer(layer.id, session)
229
259
  }
@@ -320,6 +350,9 @@ class PhotoVideoEditorActivity : Activity() {
320
350
  previewContainer.addView(overlay, FrameLayout.LayoutParams(MATCH, MATCH))
321
351
 
322
352
  val layers = LayerOverlayView(this)
353
+ layers.swappableLayerId = initialStickerLayerId
354
+ layers.stickerSwapEnabled = initialStickerIds.size > 1
355
+ layers.onStickerSwap = { switchInitialSticker() }
323
356
  layerOverlay = layers
324
357
  previewContainer.addView(layers, FrameLayout.LayoutParams(MATCH, MATCH))
325
358
 
@@ -1501,6 +1534,9 @@ class PhotoVideoEditorActivity : Activity() {
1501
1534
  if (videoCropMode) videoCropOverlay.restore(videoMediaBounds(session, playerView, false), session.state.crop)
1502
1535
  }
1503
1536
  val layerOverlay = LayerOverlayView(this)
1537
+ layerOverlay.swappableLayerId = initialStickerLayerId
1538
+ layerOverlay.stickerSwapEnabled = initialStickerIds.size > 1
1539
+ layerOverlay.onStickerSwap = { switchInitialSticker() }
1504
1540
  videoLayerOverlay = layerOverlay
1505
1541
  preview.addView(layerOverlay, FrameLayout.LayoutParams(MATCH, MATCH))
1506
1542
  layerOverlay.onLayerDelete = { id ->
@@ -2254,7 +2290,7 @@ class PhotoVideoEditorActivity : Activity() {
2254
2290
  finishWithError("Nothing to export.", "E_INTERNAL")
2255
2291
  return
2256
2292
  }
2257
- session.player.pause()
2293
+ session.stopPreviewForExport()
2258
2294
  videoExportProgressView.visibility = View.VISIBLE
2259
2295
  val exporter = VideoExporter(applicationContext)
2260
2296
  videoExporter = exporter
@@ -2285,11 +2321,13 @@ class PhotoVideoEditorActivity : Activity() {
2285
2321
  runOnUiThread {
2286
2322
  videoExporter = null
2287
2323
  videoExportProgressView.visibility = View.GONE
2324
+ session.restorePreviewAfterExport()
2288
2325
  Toast.makeText(this, error.message ?: "Unable to export the video.", Toast.LENGTH_LONG).show()
2289
2326
  }
2290
2327
  }
2291
2328
  )
2292
2329
  } catch (error: Exception) {
2330
+ session.restorePreviewAfterExport()
2293
2331
  videoExporter = null
2294
2332
  videoExportProgressView.visibility = View.GONE
2295
2333
  Toast.makeText(this, error.message ?: "Unable to export the video.", Toast.LENGTH_LONG).show()
@@ -2298,6 +2336,7 @@ class PhotoVideoEditorActivity : Activity() {
2298
2336
 
2299
2337
  private fun cancelVideoExport() {
2300
2338
  videoExporter?.cancel()
2339
+ videoSession?.restorePreviewAfterExport()
2301
2340
  videoExporter = null
2302
2341
  videoExportProgressView.visibility = View.GONE
2303
2342
  }
@@ -28,6 +28,11 @@ class LayerOverlayView(context: Context) : View(context) {
28
28
  set(value) { field = value; invalidate() }
29
29
  var selectedLayerId: String? = null
30
30
  set(value) { field = value; invalidate() }
31
+ var swappableLayerId: String? = null
32
+ set(value) { field = value; invalidate() }
33
+ var stickerSwapEnabled = false
34
+ set(value) { field = value; invalidate() }
35
+ var onStickerSwap: (() -> Unit)? = null
31
36
  var onLayerDelete: ((String) -> Unit)? = null
32
37
  var onLayerTapped: ((String?) -> Unit)? = null
33
38
  var onLayerDoubleTapped: ((String) -> Unit)? = null
@@ -71,6 +76,10 @@ class LayerOverlayView(context: Context) : View(context) {
71
76
  }
72
77
  drawControl(canvas, -half.first, -half.second, R.drawable.ic_close, Color.rgb(255, 100, 115))
73
78
  drawControl(canvas, half.first, half.second, R.drawable.ic_fullscreen, Color.rgb(167, 139, 250))
79
+ if (selected.type == LayerType.STICKER && selected.id == swappableLayerId) {
80
+ drawControl(canvas, half.first, -half.second, R.drawable.ic_swap_sticker,
81
+ if (stickerSwapEnabled && !selected.locked) Color.rgb(167, 139, 250) else Color.GRAY)
82
+ }
74
83
  canvas.restore()
75
84
  }
76
85
 
@@ -92,6 +101,17 @@ class LayerOverlayView(context: Context) : View(context) {
92
101
  if (selected != null && !selected.locked) {
93
102
  val handle = handlePoint(selected)
94
103
  val center = centerOf(selected)
104
+ if (selected.type == LayerType.STICKER && selected.id == swappableLayerId) {
105
+ val swap = swapPoint(selected)
106
+ val swapDistance = hypot(event.x - swap.first, event.y - swap.second)
107
+ val deleteDistance = hypot(event.x - (2 * center.first - handle.first), event.y - (2 * center.second - handle.second))
108
+ val scaleDistance = hypot(event.x - handle.first, event.y - handle.second)
109
+ if (swapDistance <= handleTouchRadius && swapDistance <= deleteDistance && swapDistance <= scaleDistance) {
110
+ resetGesture()
111
+ if (stickerSwapEnabled) onStickerSwap?.invoke()
112
+ return true
113
+ }
114
+ }
95
115
  if (hypot(event.x - (2 * center.first - handle.first), event.y - (2 * center.second - handle.second)) <= handleTouchRadius) {
96
116
  onLayerDelete?.invoke(selected.id); resetGesture(); return true
97
117
  }
@@ -225,6 +245,12 @@ class LayerOverlayView(context: Context) : View(context) {
225
245
  return Pair(center.first + (half.first * cos(radians) - half.second * sin(radians)).toFloat(), center.second + (half.first * sin(radians) + half.second * cos(radians)).toFloat())
226
246
  }
227
247
 
248
+ private fun swapPoint(layer: PhotoLayer): Pair<Float, Float> {
249
+ val center = centerOf(layer); val half = selectionHalfExtents(layer)
250
+ val radians = Math.toRadians(layer.rotationDegrees.toDouble())
251
+ return Pair(center.first + (half.first * cos(radians) + half.second * sin(radians)).toFloat(), center.second + (half.first * sin(radians) - half.second * cos(radians)).toFloat())
252
+ }
253
+
228
254
  private fun isHandleHit(layer: PhotoLayer, x: Float, y: Float): Boolean {
229
255
  val handle = handlePoint(layer)
230
256
  return hypot(x - handle.first, y - handle.second) <= handleTouchRadius
@@ -0,0 +1,10 @@
1
+ package com.photovideoeditor.video.render
2
+
3
+ /** Leave heap space for the host React Native app, decoded frames, and editor bitmaps. */
4
+ internal object VideoBufferBudget {
5
+ private const val MIB = 1024L * 1024L
6
+
7
+ // A target rather than a hard heap cap: Media3 can finish a sample after reaching it.
8
+ fun targetBytes(maxHeapBytes: Long): Int =
9
+ (maxHeapBytes / 16).coerceIn(2 * MIB, 16 * MIB).toInt()
10
+ }
@@ -6,6 +6,7 @@ import androidx.media3.common.MediaItem
6
6
  import androidx.media3.common.PlaybackException
7
7
  import androidx.media3.common.Player
8
8
  import androidx.media3.exoplayer.ExoPlayer
9
+ import androidx.media3.exoplayer.DefaultLoadControl
9
10
  import com.photovideoeditor.photo.render.PhotoLayerStack
10
11
  import java.io.File
11
12
 
@@ -21,7 +22,17 @@ class VideoEditSession(context: Context, val sourceUri: String) {
21
22
  private set
22
23
  var sourceHeight = 0
23
24
  private set
24
- val player: ExoPlayer = ExoPlayer.Builder(context).build()
25
+ // Bound compressed samples independently of bitrate/duration. Time-first buffering
26
+ // can otherwise exhaust the host app's heap on high-bitrate camera footage.
27
+ val player: ExoPlayer = ExoPlayer.Builder(context)
28
+ .setLoadControl(DefaultLoadControl.Builder()
29
+ .setTargetBufferBytes(VideoBufferBudget.targetBytes(Runtime.getRuntime().maxMemory()))
30
+ .setBufferDurationsMs(2_000, 5_000, 500, 1_000)
31
+ .setPrioritizeTimeOverSizeThresholds(false)
32
+ .setBackBuffer(0, false)
33
+ .build())
34
+ .build()
35
+ private var previewStoppedForExport = false
25
36
 
26
37
  /** Text/sticker/shape overlays. Reuses the photo layer model/stack — see `PhotoLayer.startMs`/`endMs` for timing. */
27
38
  val layerStack = PhotoLayerStack()
@@ -133,6 +144,21 @@ class VideoEditSession(context: Context, val sourceUri: String) {
133
144
  }
134
145
  }
135
146
 
147
+ /** Pause alone retains samples and decoders; stop releases them while retaining the playlist/position. */
148
+ fun stopPreviewForExport() {
149
+ if (previewStoppedForExport) return
150
+ previewStoppedForExport = true
151
+ player.pause()
152
+ player.stop()
153
+ }
154
+
155
+ /** Export failure/cancellation returns to the same paused preview. */
156
+ fun restorePreviewAfterExport() {
157
+ if (!previewStoppedForExport) return
158
+ previewStoppedForExport = false
159
+ player.prepare()
160
+ }
161
+
136
162
  fun release() {
137
163
  onClipsReady = null
138
164
  onPlaybackError = null
@@ -0,0 +1,3 @@
1
+ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
2
+ <path android:fillColor="#FFFFFFFF" android:pathData="M7,7h11l-3,-3 1.4,-1.4L22,8l-5.6,5.4L15,12l3,-3H7zM17,17H6l3,3 -1.4,1.4L2,16l5.6,-5.4L9,12l-3,3h11z" />
3
+ </vector>
@@ -15,7 +15,10 @@ final class PhotoVideoEditorViewController: UIViewController {
15
15
  private let features: [String: Any]
16
16
  private let theme: [String: Any]
17
17
  private let exportOptions: [String: Any]
18
- private let initialStickerId: String?
18
+ private let initialStickerIds: [String]
19
+ private let initialStickerLayerID = UUID().uuidString
20
+ private var initialStickerImages: [String: (URL, UIImage)] = [:]
21
+ private var initialStickerLoading = false
19
22
  private let stickerAssets: [[String: Any]]
20
23
  var completion: ((PhotoVideoEditorOutcome) -> Void)?
21
24
 
@@ -119,7 +122,7 @@ final class PhotoVideoEditorViewController: UIViewController {
119
122
  theme: [String: Any] = [:],
120
123
  exportOptions: [String: Any] = [:],
121
124
  stickerAssets: [[String: Any]] = [],
122
- initialStickerId: String? = nil
125
+ initialStickerIds: [String] = []
123
126
  ) {
124
127
  self.uri = uri
125
128
  self.mediaType = mediaType
@@ -127,7 +130,7 @@ final class PhotoVideoEditorViewController: UIViewController {
127
130
  self.theme = theme
128
131
  self.exportOptions = exportOptions
129
132
  self.stickerAssets = stickerAssets
130
- self.initialStickerId = initialStickerId
133
+ self.initialStickerIds = initialStickerIds
131
134
  super.init(nibName: nil, bundle: nil)
132
135
  modalPresentationStyle = .fullScreen
133
136
  }
@@ -160,21 +163,68 @@ final class PhotoVideoEditorViewController: UIViewController {
160
163
  insertInitialSticker()
161
164
  }
162
165
 
163
- private func insertInitialSticker() {
164
- guard let id = initialStickerId,
165
- let asset = consumerStickerAssets().first(where: { $0.id == id }) else { return }
166
+ private func currentInitialSticker() -> PhotoLayer? {
167
+ (photoSession?.layerStack.layers ?? videoSession?.layerStack.layers ?? []).first { $0.id == initialStickerLayerID }
168
+ }
169
+
170
+ private func switchInitialSticker() {
171
+ guard !initialStickerLoading, initialStickerIds.count > 1 else { return }
172
+ let currentID = currentInitialSticker()?.stickerId
173
+ let currentIndex = currentID.flatMap { initialStickerIds.firstIndex(of: $0) } ?? -1
174
+ insertInitialSticker(index: (currentIndex + 1) % initialStickerIds.count)
175
+ }
176
+
177
+ private func insertInitialSticker(index: Int = 0) {
178
+ guard !initialStickerLoading, initialStickerIds.indices.contains(index) else { return }
179
+ let id = initialStickerIds[index]
180
+ guard let asset = consumerStickerAssets().first(where: { $0.id == id }) else { return }
181
+ initialStickerLoading = true
166
182
  view.isUserInteractionEnabled = false
167
- let loader = OnlineStickerSheetViewController()
168
- loader.loadRuntimeSticker(.init(id: id, uri: asset.uri)) { [weak self, loader] url, image in
169
- _ = loader // Keep the loader alive until its download finishes.
183
+ let apply: (URL?, UIImage?) -> Void = { [weak self] url, image in
170
184
  guard let self else { return }
185
+ self.initialStickerLoading = false
171
186
  self.view.isUserInteractionEnabled = true
172
187
  guard let url, let image else {
173
- self.completion?(.failure(code: "E_SOURCE_UNREADABLE", message: "The initial sticker could not be loaded."))
188
+ if self.initialStickerImages.isEmpty {
189
+ self.completion?(.failure(code: "E_SOURCE_UNREADABLE", message: "The initial sticker could not be loaded."))
190
+ } else {
191
+ let alert = UIAlertController(title: "Couldn't load sticker", message: "Please try again.", preferredStyle: .alert)
192
+ alert.addAction(UIAlertAction(title: "OK", style: .default))
193
+ self.present(alert, animated: true)
194
+ }
174
195
  return
175
196
  }
197
+ self.initialStickerImages[id] = (url, image)
176
198
  self.view.layoutIfNeeded()
177
- self.handlePickedImage(url, image: image, purpose: self.mediaType == "photo" ? .photoSticker : .videoSticker)
199
+ var layer = self.currentInitialSticker() ?? self.newSticker()
200
+ layer.id = self.initialStickerLayerID
201
+ layer.stickerId = id
202
+ layer.stickerUri = url.absoluteString
203
+ layer.overlayAspectRatio = max(image.size.width, 1) / max(image.size.height, 1)
204
+ let replace: ([PhotoLayer]) -> [PhotoLayer] = { layers in
205
+ if layers.contains(where: { $0.id == self.initialStickerLayerID }) {
206
+ return layers.map { $0.id == self.initialStickerLayerID ? layer : $0 }
207
+ }
208
+ return layers + [layer]
209
+ }
210
+ if let session = self.photoSession {
211
+ session.layerStack.commit(replace)
212
+ self.onLayerStackChanged()
213
+ self.selectLayer(layer.id)
214
+ } else if let session = self.videoSession {
215
+ session.layerStack.commit(replace)
216
+ self.onVideoLayerStackChanged()
217
+ self.selectVideoLayer(layer.id, session: session)
218
+ }
219
+ }
220
+ if let cached = initialStickerImages[id] {
221
+ apply(cached.0, cached.1)
222
+ } else {
223
+ let loader = OnlineStickerSheetViewController()
224
+ loader.loadRuntimeSticker(.init(id: id, uri: asset.uri)) { [loader] url, image in
225
+ _ = loader // Keep the loader alive until its download finishes.
226
+ apply(url, image)
227
+ }
178
228
  }
179
229
  }
180
230
 
@@ -233,7 +283,10 @@ final class PhotoVideoEditorViewController: UIViewController {
233
283
  overlay.bottomAnchor.constraint(equalTo: preview.bottomAnchor),
234
284
  ])
235
285
 
236
- let layers = LayerOverlayView()
286
+ let layers = LayerOverlayView(frame: .zero)
287
+ layers.swappableLayerID = initialStickerLayerID
288
+ layers.stickerSwapEnabled = initialStickerIds.count > 1
289
+ layers.onStickerSwap = { [weak self] in self?.switchInitialSticker() }
237
290
  layerOverlay = layers
238
291
  preview.addSubview(layers)
239
292
  layers.translatesAutoresizingMaskIntoConstraints = false
@@ -1568,7 +1621,10 @@ final class PhotoVideoEditorViewController: UIViewController {
1568
1621
  let scale = bounds.width/max(fitted.width,0.001)
1569
1622
  playerView.transform = CGAffineTransform(translationX:bounds.midX-fitted.midX,y:bounds.midY-fitted.midY).scaledBy(x:scale,y:scale)
1570
1623
  }
1571
- let videoLayers = LayerOverlayView()
1624
+ let videoLayers = LayerOverlayView(frame: .zero)
1625
+ videoLayers.swappableLayerID = initialStickerLayerID
1626
+ videoLayers.stickerSwapEnabled = initialStickerIds.count > 1
1627
+ videoLayers.onStickerSwap = { [weak self] in self?.switchInitialSticker() }
1572
1628
  videoLayerOverlay = videoLayers
1573
1629
  preview.addSubview(videoLayers)
1574
1630
  videoLayers.translatesAutoresizingMaskIntoConstraints = false
@@ -8,6 +8,9 @@ final class LayerOverlayView: UIView {
8
8
  var maxScale: CGFloat = 8
9
9
  var layers: [PhotoLayer] = [] { didSet { setNeedsDisplay() } }
10
10
  var selectedLayerID: String? { didSet { setNeedsDisplay() } }
11
+ var swappableLayerID: String? { didSet { setNeedsDisplay() } }
12
+ var stickerSwapEnabled = false { didSet { setNeedsDisplay() } }
13
+ var onStickerSwap: (() -> Void)?
11
14
  var onLayerDelete: ((String) -> Void)?
12
15
  var onLayerTapped: ((String?) -> Void)?
13
16
  var onLayerDoubleTapped: ((String) -> Void)?
@@ -52,6 +55,9 @@ final class LayerOverlayView: UIView {
52
55
  }
53
56
  drawControl(context, point: CGPoint(x: -half.width, y: -half.height), symbol: "xmark", tint: .systemRed)
54
57
  drawControl(context, point: CGPoint(x: half.width, y: half.height), symbol: "arrow.up.left.and.arrow.down.right", tint: DesignTokens.primary)
58
+ if layer.type == .sticker && layer.id == swappableLayerID {
59
+ drawControl(context, point: CGPoint(x: half.width, y: -half.height), symbol: "arrow.left.arrow.right", tint: stickerSwapEnabled && !layer.locked ? DesignTokens.primary : .gray)
60
+ }
55
61
  context.restoreGState()
56
62
  }
57
63
 
@@ -80,6 +86,14 @@ final class LayerOverlayView: UIView {
80
86
  if let selected, !selected.locked {
81
87
  let center = center(of: selected); let handle = handlePoint(selected)
82
88
  let deletePoint = CGPoint(x: center.x * 2 - handle.x, y: center.y * 2 - handle.y)
89
+ if selected.type == .sticker && selected.id == swappableLayerID {
90
+ let swapDistance = distance(swapPoint(selected), point)
91
+ if swapDistance <= handleTouchRadius && swapDistance <= distance(deletePoint, point) && swapDistance <= distance(handle, point) {
92
+ resetGesture()
93
+ if stickerSwapEnabled { onStickerSwap?() }
94
+ return
95
+ }
96
+ }
83
97
  if distance(deletePoint, point) <= handleTouchRadius { onLayerDelete?(selected.id); resetGesture(); return }
84
98
  }
85
99
  let handleHit = selected.flatMap { !$0.locked && isHandleHit($0, point) ? $0 : nil }
@@ -178,6 +192,10 @@ final class LayerOverlayView: UIView {
178
192
  let center = center(of: layer); let half = selectionHalfExtents(layer); let radians = layer.rotationDegrees * .pi / 180
179
193
  return CGPoint(x: center.x + half.width * cos(radians) - half.height * sin(radians), y: center.y + half.width * sin(radians) + half.height * cos(radians))
180
194
  }
195
+ private func swapPoint(_ layer: PhotoLayer) -> CGPoint {
196
+ let center = center(of: layer); let half = selectionHalfExtents(layer); let radians = layer.rotationDegrees * .pi / 180
197
+ return CGPoint(x: center.x + half.width * cos(radians) + half.height * sin(radians), y: center.y + half.width * sin(radians) - half.height * cos(radians))
198
+ }
181
199
  private func isHandleHit(_ layer: PhotoLayer, _ point: CGPoint) -> Bool { distance(handlePoint(layer), point) <= handleTouchRadius }
182
200
  private func hitTest(_ point: CGPoint) -> PhotoLayer? {
183
201
  for layer in layers.reversed() where layer.visible {
@@ -31,7 +31,7 @@ public class PhotoVideoEditorSwift: NSObject {
31
31
  theme: payload["theme"] as? [String: Any] ?? [:],
32
32
  exportOptions: payload["export"] as? [String: Any] ?? [:],
33
33
  stickerAssets: payload["stickerAssets"] as? [[String: Any]] ?? [],
34
- initialStickerId: payload["initialStickerId"] as? String
34
+ initialStickerIds: payload["initialStickerIds"] as? [String] ?? []
35
35
  )
36
36
  controller.completion = { [weak self, weak controller] outcome in
37
37
  guard let self else { return }
@@ -39,10 +39,13 @@ function normalizeOptions(input) {
39
39
  positive(output.frameRate, 'export.frameRate');
40
40
  if (input.source.type === 'photo' && output.videoFormat !== undefined) invalid('videoFormat is only valid for video sources.');
41
41
  if (input.source.type === 'video' && output.imageFormat !== undefined) invalid('imageFormat is only valid for photo sources.');
42
- if (input.initialStickerId !== undefined) {
43
- if (typeof input.initialStickerId !== 'string' || !input.initialStickerId.trim()) invalid('initialStickerId must be a non-empty string.');
44
- const matches = Array.isArray(input.stickerAssets) ? input.stickerAssets.filter(asset => asset?.id === input.initialStickerId) : [];
45
- if (matches.length !== 1 || typeof matches[0]?.uri !== 'string' || !matches[0].uri.trim()) invalid('initialStickerId must match exactly one stickerAssets entry with a non-empty uri.');
42
+ if (input.initialStickerIds !== undefined) {
43
+ if (!Array.isArray(input.initialStickerIds) || input.initialStickerIds.some(id => typeof id !== 'string' || !id.trim())) invalid('initialStickerIds must be an array of non-empty strings.');
44
+ if (new Set(input.initialStickerIds).size !== input.initialStickerIds.length) invalid('initialStickerIds must contain unique IDs.');
45
+ for (const id of input.initialStickerIds) {
46
+ const matches = Array.isArray(input.stickerAssets) ? input.stickerAssets.filter(asset => asset?.id === id) : [];
47
+ if (matches.length !== 1 || typeof matches[0]?.uri !== 'string' || !matches[0].uri.trim()) invalid('Each initialStickerIds entry must match exactly one stickerAssets entry with a non-empty uri.');
48
+ }
46
49
  }
47
50
  const features = {
48
51
  ...defaultFeatures,
@@ -1 +1 @@
1
- {"version":3,"names":["NativePhotoVideoEditor","PhotoVideoEditorError","normalizeEditorError","addEditorEventListener","addExportProgressListener","EDITOR_PROJECT_SCHEMA_VERSION","mediaTypes","Set","qualities","imageFormats","videoFormats","defaultFeatures","crop","rotate","trim","filters","text","stickers","draw","overlays","invalid","message","positive","value","name","undefined","Number","isFinite","normalizeOptions","input","source","uri","has","type","output","export","quality","imageFormat","videoFormat","maxWidth","maxHeight","videoBitrate","frameRate","initialStickerId","matches","Array","isArray","stickerAssets","filter","asset","id","length","features","preserveMetadata","saveToGallery","openEditor","options","JSON","parse","stringify","error","openPhotoEditor","openVideoEditor","cancelExport","jobId","isAvailable"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["NativePhotoVideoEditor","PhotoVideoEditorError","normalizeEditorError","addEditorEventListener","addExportProgressListener","EDITOR_PROJECT_SCHEMA_VERSION","mediaTypes","Set","qualities","imageFormats","videoFormats","defaultFeatures","crop","rotate","trim","filters","text","stickers","draw","overlays","invalid","message","positive","value","name","undefined","Number","isFinite","normalizeOptions","input","source","uri","has","type","output","export","quality","imageFormat","videoFormat","maxWidth","maxHeight","videoBitrate","frameRate","initialStickerIds","Array","isArray","some","id","size","length","matches","stickerAssets","filter","asset","features","preserveMetadata","saveToGallery","openEditor","options","JSON","parse","stringify","error","openPhotoEditor","openVideoEditor","cancelExport","jobId","isAvailable"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAIZ,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,YAAS,CAAC;AAEjB,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,aAAU,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAU,CAAC;AACjD,mBAAmB,YAAS,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAW,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,cAAW,CAAC;AA0F/C,mEAAmE;AACnE,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAUvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,0FAA0F;AAC1F,wBAAsB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMnE;AACD,4EAA4E;AAC5E,wBAAgB,WAAW,IAAI,OAAO,CAErC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAIZ,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,YAAS,CAAC;AAEjB,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,aAAU,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAU,CAAC;AACjD,mBAAmB,YAAS,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAW,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,cAAW,CAAC;AA8F/C,mEAAmE;AACnE,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAUvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,0FAA0F;AAC1F,wBAAsB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMnE;AACD,4EAA4E;AAC5E,wBAAgB,WAAW,IAAI,OAAO,CAErC"}
@@ -62,7 +62,7 @@ export interface EditorOptions {
62
62
  /** Native UI theme. */ theme?: EditorTheme;
63
63
  /** Export preferences. */ export?: ExportOptions;
64
64
  /** Requests saving to the gallery. Currently ignored by the placeholder. */ saveToGallery?: boolean;
65
- /** ID in stickerAssets to insert at the media center when the editor opens. */ initialStickerId?: string;
65
+ /** Ordered, unique IDs in stickerAssets. The first starts centered; the sticker’s top-right control cycles through the rest. */ initialStickerIds?: string[];
66
66
  /** Consumer-provided stickers offered by the Stickers tool, in addition to the small built-in set and end-user uploads. */ stickerAssets?: StickerAsset[];
67
67
  /** Consumer-provided fonts selectable from the Text tool. */ fonts?: FontAsset[];
68
68
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-photo-video-editor",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "description": "Native photo and video editor for React Native, powered by Kotlin and Swift.",
5
5
  "main": "./lib/module/index.js",
6
6
  "module": "./lib/module/index.js",
package/src/index.ts CHANGED
@@ -72,25 +72,29 @@ function normalizeOptions(input: EditorOptions): EditorOptions {
72
72
  invalid('videoFormat is only valid for video sources.');
73
73
  if (input.source.type === 'video' && output.imageFormat !== undefined)
74
74
  invalid('imageFormat is only valid for photo sources.');
75
- if (input.initialStickerId !== undefined) {
75
+ if (input.initialStickerIds !== undefined) {
76
76
  if (
77
- typeof input.initialStickerId !== 'string' ||
78
- !input.initialStickerId.trim()
77
+ !Array.isArray(input.initialStickerIds) ||
78
+ input.initialStickerIds.some((id) => typeof id !== 'string' || !id.trim())
79
79
  )
80
- invalid('initialStickerId must be a non-empty string.');
81
- const matches = Array.isArray(input.stickerAssets)
82
- ? input.stickerAssets.filter(
83
- (asset) => asset?.id === input.initialStickerId
84
- )
85
- : [];
80
+ invalid('initialStickerIds must be an array of non-empty strings.');
86
81
  if (
87
- matches.length !== 1 ||
88
- typeof matches[0]?.uri !== 'string' ||
89
- !matches[0].uri.trim()
82
+ new Set(input.initialStickerIds).size !== input.initialStickerIds.length
90
83
  )
91
- invalid(
92
- 'initialStickerId must match exactly one stickerAssets entry with a non-empty uri.'
93
- );
84
+ invalid('initialStickerIds must contain unique IDs.');
85
+ for (const id of input.initialStickerIds) {
86
+ const matches = Array.isArray(input.stickerAssets)
87
+ ? input.stickerAssets.filter((asset) => asset?.id === id)
88
+ : [];
89
+ if (
90
+ matches.length !== 1 ||
91
+ typeof matches[0]?.uri !== 'string' ||
92
+ !matches[0].uri.trim()
93
+ )
94
+ invalid(
95
+ 'Each initialStickerIds entry must match exactly one stickerAssets entry with a non-empty uri.'
96
+ );
97
+ }
94
98
  }
95
99
  const features = { ...defaultFeatures, ...input.features };
96
100
  if (input.source.type === 'photo') {
package/src/types.ts CHANGED
@@ -70,7 +70,7 @@ export interface EditorOptions {
70
70
  /** Native UI theme. */ theme?: EditorTheme;
71
71
  /** Export preferences. */ export?: ExportOptions;
72
72
  /** Requests saving to the gallery. Currently ignored by the placeholder. */ saveToGallery?: boolean;
73
- /** ID in stickerAssets to insert at the media center when the editor opens. */ initialStickerId?: string;
73
+ /** Ordered, unique IDs in stickerAssets. The first starts centered; the sticker’s top-right control cycles through the rest. */ initialStickerIds?: string[];
74
74
  /** Consumer-provided stickers offered by the Stickers tool, in addition to the small built-in set and end-user uploads. */ stickerAssets?: StickerAsset[];
75
75
  /** Consumer-provided fonts selectable from the Text tool. */ fonts?: FontAsset[];
76
76
  }