react-native-photo-video-editor 0.3.0 → 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 +4 -0
- package/android/src/main/java/com/photovideoeditor/editor/PhotoVideoEditorActivity.kt +4 -1
- package/android/src/main/java/com/photovideoeditor/video/render/VideoBufferBudget.kt +10 -0
- package/android/src/main/java/com/photovideoeditor/video/render/VideoEditSession.kt +27 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
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
|
+
|
|
5
9
|
## 0.3.0
|
|
6
10
|
|
|
7
11
|
- Breaking API change: migrate `initialStickerId: 'brand'` to `initialStickerIds: ['brand']`.
|
|
@@ -2290,7 +2290,7 @@ class PhotoVideoEditorActivity : Activity() {
|
|
|
2290
2290
|
finishWithError("Nothing to export.", "E_INTERNAL")
|
|
2291
2291
|
return
|
|
2292
2292
|
}
|
|
2293
|
-
session.
|
|
2293
|
+
session.stopPreviewForExport()
|
|
2294
2294
|
videoExportProgressView.visibility = View.VISIBLE
|
|
2295
2295
|
val exporter = VideoExporter(applicationContext)
|
|
2296
2296
|
videoExporter = exporter
|
|
@@ -2321,11 +2321,13 @@ class PhotoVideoEditorActivity : Activity() {
|
|
|
2321
2321
|
runOnUiThread {
|
|
2322
2322
|
videoExporter = null
|
|
2323
2323
|
videoExportProgressView.visibility = View.GONE
|
|
2324
|
+
session.restorePreviewAfterExport()
|
|
2324
2325
|
Toast.makeText(this, error.message ?: "Unable to export the video.", Toast.LENGTH_LONG).show()
|
|
2325
2326
|
}
|
|
2326
2327
|
}
|
|
2327
2328
|
)
|
|
2328
2329
|
} catch (error: Exception) {
|
|
2330
|
+
session.restorePreviewAfterExport()
|
|
2329
2331
|
videoExporter = null
|
|
2330
2332
|
videoExportProgressView.visibility = View.GONE
|
|
2331
2333
|
Toast.makeText(this, error.message ?: "Unable to export the video.", Toast.LENGTH_LONG).show()
|
|
@@ -2334,6 +2336,7 @@ class PhotoVideoEditorActivity : Activity() {
|
|
|
2334
2336
|
|
|
2335
2337
|
private fun cancelVideoExport() {
|
|
2336
2338
|
videoExporter?.cancel()
|
|
2339
|
+
videoSession?.restorePreviewAfterExport()
|
|
2337
2340
|
videoExporter = null
|
|
2338
2341
|
videoExportProgressView.visibility = View.GONE
|
|
2339
2342
|
}
|
|
@@ -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
|
-
|
|
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
|
package/package.json
CHANGED