munim-ffmpeg 0.7.1 β†’ 0.8.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 (26) hide show
  1. package/README.md +57 -4
  2. package/android/src/main/java/com/margelo/nitro/munimffmpeg/FFmpegNative.kt +11 -0
  3. package/android/src/main/java/com/margelo/nitro/munimffmpeg/HybridMunimFfmpeg.kt +94 -13
  4. package/ios/HybridMunimFfmpeg.swift +102 -14
  5. package/ios/munim_ffmpeg_core.h +35 -0
  6. package/lib/index.d.ts +16 -2
  7. package/lib/index.js +20 -0
  8. package/lib/specs/MunimFfmpeg.nitro.d.ts +15 -0
  9. package/nitrogen/generated/android/c++/JFFmpegSessionState.hpp +73 -0
  10. package/nitrogen/generated/android/c++/JHybridMunimFfmpegSpec.cpp +19 -0
  11. package/nitrogen/generated/android/c++/JHybridMunimFfmpegSpec.hpp +3 -0
  12. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimffmpeg/FFmpegSessionState.kt +28 -0
  13. package/nitrogen/generated/android/kotlin/com/margelo/nitro/munimffmpeg/HybridMunimFfmpegSpec.kt +12 -0
  14. package/nitrogen/generated/ios/NitroMunimFfmpeg-Swift-Cxx-Bridge.hpp +21 -0
  15. package/nitrogen/generated/ios/NitroMunimFfmpeg-Swift-Cxx-Umbrella.hpp +3 -0
  16. package/nitrogen/generated/ios/c++/HybridMunimFfmpegSpecSwift.hpp +27 -0
  17. package/nitrogen/generated/ios/swift/FFmpegSessionState.swift +60 -0
  18. package/nitrogen/generated/ios/swift/HybridMunimFfmpegSpec.swift +3 -0
  19. package/nitrogen/generated/ios/swift/HybridMunimFfmpegSpec_cxx.swift +36 -0
  20. package/nitrogen/generated/shared/c++/FFmpegSessionState.hpp +96 -0
  21. package/nitrogen/generated/shared/c++/HybridMunimFfmpegSpec.cpp +3 -0
  22. package/nitrogen/generated/shared/c++/HybridMunimFfmpegSpec.hpp +6 -0
  23. package/package.json +1 -1
  24. package/scripts/binaries.json +1 -1
  25. package/src/index.ts +25 -0
  26. package/src/specs/MunimFfmpeg.nitro.ts +26 -0
package/README.md CHANGED
@@ -100,6 +100,7 @@
100
100
  - πŸ“ˆ **Encoding statistics:** Track time, size, bitrate, speed, frames, FPS, and quality
101
101
  - 🎯 **Targeted cancellation:** Capture a native session ID immediately and cancel only that command
102
102
  - πŸ›‘ **Global cancellation:** Stop all active sessions during workflow or screen cleanup
103
+ - ⏸️ **Pause and resume:** Hold a running or queued session with no CPU use and continue exactly where it stopped
103
104
 
104
105
  ### FFprobe and media inspection
105
106
 
@@ -132,6 +133,8 @@
132
133
  | Immediate session ID | βœ… | βœ… | `onSessionCreated` fires after the native session is created. |
133
134
  | Cancel one FFmpeg session | βœ… | βœ… | Pass the positive safe-integer ID received by `execute`'s `onSessionCreated`. The native dependency does not expose FFprobe cancellation. |
134
135
  | Cancel all FFmpeg sessions | βœ… | βœ… | Use `cancelAll()` or call `cancel()` without an ID. |
136
+ | Pause and resume a session | βœ… | βœ… | `pause()` / `resume()` with an `execute` session ID, running or queued. FFprobe sessions cannot be paused. |
137
+ | Session state | βœ… | βœ… | `getSessionState()` reports `queued`, `running`, `paused`, `completed`, `failed`, or `cancelled`. |
135
138
  | Expo Go | ❌ | ❌ | A native development build is required. |
136
139
  | Capability discovery | βœ… | βœ… | `listEncoders()`, `listDecoders()`, `listMuxers()`, `listDemuxers()`, `listFilters()`, `listProtocols()`, and `pickEncoder()` report what the bundled build supports. |
137
140
  | Subtitle burn-in | βœ… | βœ… | libass with system fonts: Core Text on iOS, fontconfig over `/system/fonts` on Android. |
@@ -144,13 +147,12 @@ Codec availability is determined by the native FFmpeg builds described in [Bundl
144
147
 
145
148
  ## Where this is verified
146
149
 
147
- Every release runs the example's 25-check device suite. For 0.4.x:
150
+ Every release runs the example's device suite (33 checks, including pause/resume). For 0.8.x:
148
151
 
149
152
  | Target | Result |
150
153
  | -------------------------- | ---------------------------------------------------------------- |
151
- | iPad Air (M3), iOS 26 | 25/25 |
152
- | iOS Simulator, arm64 | 25/25 |
153
- | Galaxy A14 5G, arm64-v8a | 25/25 |
154
+ | iPad Air (M3), iPadOS 27 | 33/33 |
155
+ | Galaxy A14 5G, arm64-v8a | 33/33 |
154
156
  | Android emulator, arm64 | Software encoding passes; hardware encoding does not β€” see below |
155
157
  | Android emulator, `x86_64` | Same: `libopenh264` passes, MediaCodec does not |
156
158
  | Android `armeabi-v7a` | Built and statically checked, not executed |
@@ -473,6 +475,57 @@ Cancels every active FFmpeg session.
473
475
  function cancelAll(): void
474
476
  ```
475
477
 
478
+ ### `pause(sessionId)` and `resume(sessionId)`
479
+
480
+ Pauses a session started with `execute()`, whether it is running or still queued behind another one, and resumes it later.
481
+
482
+ ```typescript
483
+ function pause(sessionId: number): boolean
484
+ function resume(sessionId: number): boolean
485
+ ```
486
+
487
+ FFmpeg itself has no pause, so munim-ffmpeg stops the threads that read its inputs. Everything downstream finishes the frames it already has and then waits, so a paused session uses no CPU. Output files stay open, and `resume()` carries on from the exact packet where reading stopped. Nothing is re-encoded, skipped or duplicated, and the `execute()` promise settles once, when the work is done. `-re` and `-readrate` inputs have their clocks shifted by the time spent paused, so they do not rush to catch up afterwards.
488
+
489
+ `pause()` returns `false` if the session is unknown, has already finished, or is an FFprobe session. `resume()` returns `false` if the session was not paused. `cancel()` and `cancelAll()` work while paused.
490
+
491
+ Notes:
492
+
493
+ - The statistics callback keeps firing while paused, reporting the same `timeMs`. `speed` includes the paused time.
494
+ - Live network inputs are not read while paused. Servers that drop idle connections may end the session when it resumes.
495
+
496
+ ```typescript
497
+ import { execute, pause, resume } from 'munim-ffmpeg'
498
+
499
+ let sessionId: number | undefined
500
+ const done = execute(args, undefined, undefined, (id) => {
501
+ sessionId = id
502
+ })
503
+
504
+ // Later, from a pause button:
505
+ pause(sessionId!)
506
+ // …and from a resume button:
507
+ resume(sessionId!)
508
+
509
+ const result = await done
510
+ ```
511
+
512
+ ### `getSessionState(sessionId)`
513
+
514
+ ```typescript
515
+ type FFmpegSessionState =
516
+ | 'queued'
517
+ | 'running'
518
+ | 'paused'
519
+ | 'completed'
520
+ | 'failed'
521
+ | 'cancelled'
522
+ | 'unknown'
523
+
524
+ function getSessionState(sessionId: number): FFmpegSessionState
525
+ ```
526
+
527
+ `queued` sessions are waiting for the one running ahead of them, since FFmpeg runs one execution at a time. `unknown` means the ID was never issued, or it finished long enough ago that its record was dropped (the last 512 finished sessions are kept).
528
+
476
529
  ### `getFFmpegVersion()`
477
530
 
478
531
  Returns the version reported by the bundled native FFmpeg library.
@@ -79,16 +79,27 @@ object FFmpegNative {
79
79
  arguments: Array<String>,
80
80
  stdoutPath: String,
81
81
  session: FFmpegSession,
82
+ sessionId: Long,
82
83
  ): Int
83
84
 
84
85
  external fun nativeExecuteProbe(
85
86
  arguments: Array<String>,
86
87
  outputPath: String,
87
88
  session: FFmpegSession,
89
+ sessionId: Long,
88
90
  ): Int
89
91
 
90
92
  external fun nativeCancel()
91
93
 
94
+ external fun nativePause(sessionId: Long): Boolean
95
+
96
+ external fun nativeResume(sessionId: Long): Boolean
97
+
98
+ external fun nativeIsPaused(sessionId: Long): Boolean
99
+
100
+ /** Id of the execution or probe currently running, or 0. */
101
+ external fun nativeRunningSession(): Long
102
+
92
103
  /**
93
104
  * libass discovers fonts through fontconfig, and Android has no fonts.conf,
94
105
  * so one pointing at the system font directories is written to the app's
@@ -16,6 +16,7 @@ class HybridMunimFfmpeg : HybridMunimFfmpegSpec() {
16
16
  get() = FFmpegNative.nativeVersion()
17
17
 
18
18
  private val sessions = AtomicLong(0)
19
+ private val registry = SessionRegistry()
19
20
 
20
21
  private fun nextSession() = sessions.incrementAndGet().toDouble()
21
22
 
@@ -72,6 +73,7 @@ class HybridMunimFfmpeg : HybridMunimFfmpegSpec() {
72
73
  onSessionCreated: ((sessionId: Double) -> Unit)?,
73
74
  ): Promise<FFmpegSessionResult> {
74
75
  val sessionId = nextSession()
76
+ registry.register(sessionId, SessionRegistry.Kind.EXECUTE)
75
77
  onSessionCreated?.invoke(sessionId)
76
78
 
77
79
  return runOnFfmpegThread {
@@ -84,7 +86,11 @@ class HybridMunimFfmpeg : HybridMunimFfmpegSpec() {
84
86
  logSink = { message -> onLog?.invoke(message) },
85
87
  statisticsSink = onStatistics,
86
88
  )
87
- val returnCode = FFmpegNative.nativeExecute(arguments_, stdout.absolutePath, session)
89
+ val returnCode =
90
+ FFmpegNative.nativeExecute(arguments_, stdout.absolutePath, session, sessionId.toLong())
91
+ // A pause that raced the end of the run would otherwise stay listed.
92
+ FFmpegNative.nativeResume(sessionId.toLong())
93
+ registry.finish(sessionId, returnCode)
88
94
 
89
95
  val printed = runCatching { stdout.readText() }.getOrDefault("")
90
96
  stdout.delete()
@@ -99,11 +105,13 @@ class HybridMunimFfmpeg : HybridMunimFfmpegSpec() {
99
105
  onSessionCreated: ((sessionId: Double) -> Unit)?,
100
106
  ): Promise<FFmpegSessionResult> {
101
107
  val sessionId = nextSession()
108
+ registry.register(sessionId, SessionRegistry.Kind.PROBE)
102
109
  onSessionCreated?.invoke(sessionId)
103
110
 
104
111
  return runOnFfmpegThread {
105
112
  val startedAt = System.currentTimeMillis()
106
- val (returnCode, report) = runProbe(arguments_, onLog)
113
+ val (returnCode, report) = runProbe(arguments_, onLog, sessionId.toLong())
114
+ registry.finish(sessionId, returnCode)
107
115
  result(sessionId, returnCode, report, System.currentTimeMillis() - startedAt)
108
116
  }
109
117
  }
@@ -141,6 +149,7 @@ class HybridMunimFfmpeg : HybridMunimFfmpegSpec() {
141
149
  private fun runProbe(
142
150
  arguments: Array<String>,
143
151
  onLog: ((message: String) -> Unit)?,
152
+ sessionId: Long = 0,
144
153
  ): Pair<Int, String> {
145
154
  val destination = File.createTempFile("munim-ffprobe", ".txt")
146
155
  try {
@@ -149,7 +158,7 @@ class HybridMunimFfmpeg : HybridMunimFfmpegSpec() {
149
158
  statisticsSink = null,
150
159
  )
151
160
  val returnCode =
152
- FFmpegNative.nativeExecuteProbe(arguments, destination.absolutePath, session)
161
+ FFmpegNative.nativeExecuteProbe(arguments, destination.absolutePath, session, sessionId)
153
162
 
154
163
  val report = destination.readText()
155
164
  return returnCode to report.ifEmpty { session.output }
@@ -158,17 +167,19 @@ class HybridMunimFfmpeg : HybridMunimFfmpegSpec() {
158
167
  }
159
168
  }
160
169
 
161
- override fun cancel(sessionId: Double?) {
162
- if (sessionId != null) {
163
- require(
164
- sessionId.isFinite() &&
165
- sessionId > 0 &&
166
- sessionId % 1.0 == 0.0 &&
167
- sessionId <= 9_007_199_254_740_991.0,
168
- ) {
169
- "Invalid FFmpeg session ID: $sessionId. Expected a positive safe integer."
170
- }
170
+ private fun validate(sessionId: Double) {
171
+ require(
172
+ sessionId.isFinite() &&
173
+ sessionId > 0 &&
174
+ sessionId % 1.0 == 0.0 &&
175
+ sessionId <= 9_007_199_254_740_991.0,
176
+ ) {
177
+ "Invalid FFmpeg session ID: $sessionId. Expected a positive safe integer."
171
178
  }
179
+ }
180
+
181
+ override fun cancel(sessionId: Double?) {
182
+ if (sessionId != null) validate(sessionId)
172
183
  // Only one execution runs at a time, so a targeted cancel and cancelAll()
173
184
  // are the same operation.
174
185
  FFmpegNative.nativeCancel()
@@ -178,6 +189,36 @@ class HybridMunimFfmpeg : HybridMunimFfmpegSpec() {
178
189
  FFmpegNative.nativeCancel()
179
190
  }
180
191
 
192
+ override fun pause(sessionId: Double): Boolean {
193
+ validate(sessionId)
194
+ if (registry.activeKind(sessionId) != SessionRegistry.Kind.EXECUTE) return false
195
+ FFmpegNative.nativePause(sessionId.toLong())
196
+ // The run may have ended between the check and the pause.
197
+ if (registry.activeKind(sessionId) == null) {
198
+ FFmpegNative.nativeResume(sessionId.toLong())
199
+ return false
200
+ }
201
+ return true
202
+ }
203
+
204
+ override fun resume(sessionId: Double): Boolean {
205
+ validate(sessionId)
206
+ return FFmpegNative.nativeResume(sessionId.toLong())
207
+ }
208
+
209
+ override fun getSessionState(sessionId: Double): FFmpegSessionState {
210
+ validate(sessionId)
211
+ registry.finishedState(sessionId)?.let { return it }
212
+ if (registry.activeKind(sessionId) == null) return FFmpegSessionState.UNKNOWN
213
+ val id = sessionId.toLong()
214
+ if (FFmpegNative.nativeIsPaused(id)) return FFmpegSessionState.PAUSED
215
+ return if (FFmpegNative.nativeRunningSession() == id) {
216
+ FFmpegSessionState.RUNNING
217
+ } else {
218
+ FFmpegSessionState.QUEUED
219
+ }
220
+ }
221
+
181
222
  private companion object {
182
223
  private val threadCounter = AtomicInteger(0)
183
224
 
@@ -191,3 +232,43 @@ class HybridMunimFfmpeg : HybridMunimFfmpegSpec() {
191
232
  )
192
233
  }
193
234
  }
235
+
236
+ /**
237
+ * What the module remembers about each session id. Whether an unfinished
238
+ * session is queued, running or paused is asked of the native core, which is
239
+ * the only place that knows.
240
+ */
241
+ private class SessionRegistry {
242
+ enum class Kind { EXECUTE, PROBE }
243
+
244
+ private val kinds = HashMap<Double, Kind>()
245
+ private val finished = LinkedHashMap<Double, FFmpegSessionState>()
246
+
247
+ @Synchronized
248
+ fun register(sessionId: Double, kind: Kind) {
249
+ kinds[sessionId] = kind
250
+ }
251
+
252
+ @Synchronized
253
+ fun finish(sessionId: Double, returnCode: Int) {
254
+ kinds.remove(sessionId)
255
+ finished[sessionId] = when (returnCode) {
256
+ FFmpegNative.CANCELLED -> FFmpegSessionState.CANCELLED
257
+ 0 -> FFmpegSessionState.COMPLETED
258
+ else -> FFmpegSessionState.FAILED
259
+ }
260
+ // Finished sessions are forgotten, oldest first, beyond this many.
261
+ while (finished.size > FINISHED_LIMIT) finished.remove(finished.keys.first())
262
+ }
263
+
264
+ /** null once finished or never issued. */
265
+ @Synchronized
266
+ fun activeKind(sessionId: Double): Kind? = kinds[sessionId]
267
+
268
+ @Synchronized
269
+ fun finishedState(sessionId: Double): FFmpegSessionState? = finished[sessionId]
270
+
271
+ private companion object {
272
+ const val FINISHED_LIMIT = 512
273
+ }
274
+ }
@@ -55,8 +55,57 @@ private let installCallbacks: Void = munim_ffmpeg_set_callbacks({ context, messa
55
55
  session.onStatistics?(timeMs, sizeBytes, bitrate, speed, frame, fps, quality)
56
56
  }, nil)
57
57
 
58
+ /// What the module remembers about each session id. Whether an unfinished
59
+ /// session is queued, running or paused is asked of the C core, which is the
60
+ /// only place that knows.
61
+ private final class SessionRegistry {
62
+ enum Kind { case execute, probe }
63
+
64
+ private var kinds: [Double: Kind] = [:]
65
+ private var finished: [Double: FFmpegSessionState] = [:]
66
+ private var finishedOrder: [Double] = []
67
+ private let lock = NSLock()
68
+ /// Finished sessions are forgotten, oldest first, beyond this many.
69
+ private let finishedLimit = 512
70
+
71
+ func register(_ sessionId: Double, _ kind: Kind) {
72
+ lock.lock()
73
+ kinds[sessionId] = kind
74
+ lock.unlock()
75
+ }
76
+
77
+ func finish(_ sessionId: Double, returnCode: Int32) {
78
+ let state: FFmpegSessionState =
79
+ returnCode == Int32(MUNIM_FFMPEG_CANCELLED) ? .cancelled
80
+ : returnCode == 0 ? .completed
81
+ : .failed
82
+ lock.lock()
83
+ kinds[sessionId] = nil
84
+ finished[sessionId] = state
85
+ finishedOrder.append(sessionId)
86
+ if finishedOrder.count > finishedLimit {
87
+ finished[finishedOrder.removeFirst()] = nil
88
+ }
89
+ lock.unlock()
90
+ }
91
+
92
+ /// nil once finished or never issued.
93
+ func activeKind(_ sessionId: Double) -> Kind? {
94
+ lock.lock()
95
+ defer { lock.unlock() }
96
+ return kinds[sessionId]
97
+ }
98
+
99
+ func finishedState(_ sessionId: Double) -> FFmpegSessionState? {
100
+ lock.lock()
101
+ defer { lock.unlock() }
102
+ return finished[sessionId]
103
+ }
104
+ }
105
+
58
106
  final class HybridMunimFfmpeg: HybridMunimFfmpegSpec {
59
107
  private var sessionCounter: Double = 0
108
+ private let registry = SessionRegistry()
60
109
 
61
110
  var ffmpegVersion: String {
62
111
  String(cString: munim_ffmpeg_version())
@@ -99,6 +148,7 @@ final class HybridMunimFfmpeg: HybridMunimFfmpegSpec {
99
148
  ) throws -> Promise<FFmpegSessionResult> {
100
149
  let promise = Promise<FFmpegSessionResult>()
101
150
  let sessionId = nextSession()
151
+ registry.register(sessionId, .execute)
102
152
  onSessionCreated?(sessionId)
103
153
 
104
154
  executionQueue.async {
@@ -112,14 +162,18 @@ final class HybridMunimFfmpeg: HybridMunimFfmpegSpec {
112
162
 
113
163
  let returnCode = withExtendedLifetime(session) {
114
164
  withArrayOfCStrings(arguments_) { argv in
115
- munim_ffmpeg_execute_ctx(
165
+ munim_ffmpeg_execute_session(
116
166
  Int32(arguments_.count),
117
167
  argv,
118
168
  printedPath,
119
- Unmanaged.passUnretained(session).toOpaque()
169
+ Unmanaged.passUnretained(session).toOpaque(),
170
+ Int64(sessionId)
120
171
  )
121
172
  }
122
173
  }
174
+ // A pause that raced the end of the run would otherwise stay listed.
175
+ _ = munim_ffmpeg_resume(Int64(sessionId))
176
+ self.registry.finish(sessionId, returnCode: returnCode)
123
177
 
124
178
  let printed = (try? String(contentsOfFile: printedPath, encoding: .utf8)) ?? ""
125
179
  try? FileManager.default.removeItem(atPath: printedPath)
@@ -144,11 +198,13 @@ final class HybridMunimFfmpeg: HybridMunimFfmpegSpec {
144
198
  ) throws -> Promise<FFmpegSessionResult> {
145
199
  let promise = Promise<FFmpegSessionResult>()
146
200
  let sessionId = nextSession()
201
+ registry.register(sessionId, .probe)
147
202
  onSessionCreated?(sessionId)
148
203
 
149
204
  executionQueue.async {
150
205
  let startedAt = Date()
151
- let (returnCode, report) = Self.runProbe(arguments_, onLog: onLog)
206
+ let (returnCode, report) = Self.runProbe(arguments_, onLog: onLog, sessionId: sessionId)
207
+ self.registry.finish(sessionId, returnCode: returnCode)
152
208
  promise.resolve(
153
209
  withResult: self.result(
154
210
  sessionId: sessionId,
@@ -195,7 +251,8 @@ final class HybridMunimFfmpeg: HybridMunimFfmpegSpec {
195
251
  /// so it is pointed at a temporary file with `-o` and read back.
196
252
  private static func runProbe(
197
253
  _ arguments: [String],
198
- onLog: ((String) -> Void)?
254
+ onLog: ((String) -> Void)?,
255
+ sessionId: Double = 0
199
256
  ) -> (Int32, String) {
200
257
  let destination = temporaryFile()
201
258
 
@@ -204,11 +261,12 @@ final class HybridMunimFfmpeg: HybridMunimFfmpegSpec {
204
261
 
205
262
  let returnCode = withExtendedLifetime(session) {
206
263
  withArrayOfCStrings(arguments) { argv in
207
- munim_ffmpeg_probe_ctx(
264
+ munim_ffmpeg_probe_session(
208
265
  Int32(arguments.count),
209
266
  argv,
210
267
  destination,
211
- Unmanaged.passUnretained(session).toOpaque()
268
+ Unmanaged.passUnretained(session).toOpaque(),
269
+ Int64(sessionId)
212
270
  )
213
271
  }
214
272
  }
@@ -219,16 +277,20 @@ final class HybridMunimFfmpeg: HybridMunimFfmpegSpec {
219
277
  return (returnCode, report.isEmpty ? session.output : report)
220
278
  }
221
279
 
280
+ private static func validate(_ sessionId: Double) throws {
281
+ guard
282
+ sessionId.isFinite,
283
+ sessionId > 0,
284
+ sessionId.rounded(.towardZero) == sessionId,
285
+ sessionId <= 9_007_199_254_740_991
286
+ else {
287
+ throw MunimFfmpegError.invalidSessionId(sessionId)
288
+ }
289
+ }
290
+
222
291
  func cancel(sessionId: Double?) throws {
223
292
  if let sessionId {
224
- guard
225
- sessionId.isFinite,
226
- sessionId > 0,
227
- sessionId.rounded(.towardZero) == sessionId,
228
- sessionId <= 9_007_199_254_740_991
229
- else {
230
- throw MunimFfmpegError.invalidSessionId(sessionId)
231
- }
293
+ try Self.validate(sessionId)
232
294
  }
233
295
  // One execution runs at a time, so cancelling a specific session and
234
296
  // cancelling everything are the same operation.
@@ -238,6 +300,32 @@ final class HybridMunimFfmpeg: HybridMunimFfmpegSpec {
238
300
  func cancelAll() throws {
239
301
  munim_ffmpeg_cancel()
240
302
  }
303
+
304
+ func pause(sessionId: Double) throws -> Bool {
305
+ try Self.validate(sessionId)
306
+ guard registry.activeKind(sessionId) == .execute else { return false }
307
+ _ = munim_ffmpeg_pause(Int64(sessionId))
308
+ // The run may have ended between the check and the pause; the core drops
309
+ // the pause of a finished session, so report what actually happened.
310
+ if registry.activeKind(sessionId) == nil {
311
+ _ = munim_ffmpeg_resume(Int64(sessionId))
312
+ return false
313
+ }
314
+ return true
315
+ }
316
+
317
+ func resume(sessionId: Double) throws -> Bool {
318
+ try Self.validate(sessionId)
319
+ return munim_ffmpeg_resume(Int64(sessionId)) != 0
320
+ }
321
+
322
+ func getSessionState(sessionId: Double) throws -> FFmpegSessionState {
323
+ try Self.validate(sessionId)
324
+ if let state = registry.finishedState(sessionId) { return state }
325
+ guard registry.activeKind(sessionId) != nil else { return .unknown }
326
+ if munim_ffmpeg_is_paused(Int64(sessionId)) != 0 { return .paused }
327
+ return munim_ffmpeg_running_session() == Int64(sessionId) ? .running : .queued
328
+ }
241
329
  }
242
330
 
243
331
  /// Builds a C `argv` that stays valid for the duration of `body`.
@@ -61,6 +61,41 @@ int munim_ffmpeg_probe_ctx(int argc, const char *const *argv,
61
61
  */
62
62
  void munim_ffmpeg_cancel(void);
63
63
 
64
+ /**
65
+ * Like munim_ffmpeg_execute_ctx, for a run identified by `session_id` (> 0) so
66
+ * it can be paused and resumed. munim_ffmpeg_execute_ctx runs with id 0, which
67
+ * cannot be paused.
68
+ */
69
+ int munim_ffmpeg_execute_session(int argc, const char *const *argv,
70
+ const char *stdout_path, void *context,
71
+ long long session_id);
72
+
73
+ /**
74
+ * Pauses the execution with `session_id`. If it is running, its input threads
75
+ * stop reading and the rest of the pipeline idles once it has drained; if it is
76
+ * still queued, it will start paused. Output files stay open and
77
+ * munim_ffmpeg_resume() continues exactly where it stopped. Cancelling works
78
+ * as usual and also clears every pause. Returns 1 if the session is running.
79
+ */
80
+ int munim_ffmpeg_pause(long long session_id);
81
+
82
+ /** Resumes `session_id`. Returns 1 if it was paused. */
83
+ int munim_ffmpeg_resume(long long session_id);
84
+
85
+ /** Returns 1 while `session_id` is paused. */
86
+ int munim_ffmpeg_is_paused(long long session_id);
87
+
88
+ /**
89
+ * Id of the execution or probe currently running, or 0. Probes started through
90
+ * munim_ffmpeg_probe_session report their id here but cannot be paused.
91
+ */
92
+ long long munim_ffmpeg_running_session(void);
93
+
94
+ /** Like munim_ffmpeg_probe_ctx, for a run identified by `session_id`. */
95
+ int munim_ffmpeg_probe_session(int argc, const char *const *argv,
96
+ const char *output_path, void *context,
97
+ long long session_id);
98
+
64
99
  /** Return code reported when a run was cancelled. */
65
100
  #define MUNIM_FFMPEG_CANCELLED 255
66
101
 
package/lib/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { FFmpegLogCallback, FFmpegSessionResult, FFmpegSessionCreatedCallback, FFmpegStatisticsCallback, MunimFfmpeg as MunimFfmpegSpec } from './specs/MunimFfmpeg.nitro';
1
+ import type { FFmpegLogCallback, FFmpegSessionResult, FFmpegSessionCreatedCallback, FFmpegSessionState, FFmpegStatisticsCallback, MunimFfmpeg as MunimFfmpegSpec } from './specs/MunimFfmpeg.nitro';
2
2
  declare const MunimFfmpeg: MunimFfmpegSpec;
3
- export type { FFmpegLogCallback, FFmpegSessionResult, FFmpegSessionCreatedCallback, FFmpegStatisticsCallback, MunimFfmpegSpec, };
3
+ export type { FFmpegLogCallback, FFmpegSessionResult, FFmpegSessionCreatedCallback, FFmpegSessionState, FFmpegStatisticsCallback, MunimFfmpegSpec, };
4
4
  /**
5
5
  * Converts a `file://` URI into the plain path FFmpeg expects.
6
6
  *
@@ -98,6 +98,20 @@ export declare function getMediaInformation(path: string): Promise<MediaInformat
98
98
  export declare function getMediaDuration(information: MediaInformation): number | undefined;
99
99
  export declare function cancel(sessionId?: number): void;
100
100
  export declare function cancelAll(): void;
101
+ /**
102
+ * Pauses a session started with `execute()`, whether it is running or still
103
+ * queued. FFmpeg stops reading its inputs and the rest of the pipeline idles
104
+ * once it has drained, so a paused session costs no CPU. Output files stay
105
+ * open, and `resume()` continues from exactly where it stopped: nothing is
106
+ * re-encoded or skipped, and the `execute()` promise settles once, when the
107
+ * work finishes. `cancel()` still works while paused.
108
+ *
109
+ * Returns false if the session is unknown, has finished, or is a probe.
110
+ */
111
+ export declare function pause(sessionId: number): boolean;
112
+ /** Resumes a paused session. Returns false if it was not paused. */
113
+ export declare function resume(sessionId: number): boolean;
114
+ export declare function getSessionState(sessionId: number): FFmpegSessionState;
101
115
  export declare function getFFmpegVersion(): string;
102
116
  /** Encoder names the bundled FFmpeg build can write, e.g. `libx264`. */
103
117
  export declare function listEncoders(): Promise<string[]>;
package/lib/index.js CHANGED
@@ -46,6 +46,26 @@ export function cancel(sessionId) {
46
46
  export function cancelAll() {
47
47
  MunimFfmpeg.cancelAll();
48
48
  }
49
+ /**
50
+ * Pauses a session started with `execute()`, whether it is running or still
51
+ * queued. FFmpeg stops reading its inputs and the rest of the pipeline idles
52
+ * once it has drained, so a paused session costs no CPU. Output files stay
53
+ * open, and `resume()` continues from exactly where it stopped: nothing is
54
+ * re-encoded or skipped, and the `execute()` promise settles once, when the
55
+ * work finishes. `cancel()` still works while paused.
56
+ *
57
+ * Returns false if the session is unknown, has finished, or is a probe.
58
+ */
59
+ export function pause(sessionId) {
60
+ return MunimFfmpeg.pause(sessionId);
61
+ }
62
+ /** Resumes a paused session. Returns false if it was not paused. */
63
+ export function resume(sessionId) {
64
+ return MunimFfmpeg.resume(sessionId);
65
+ }
66
+ export function getSessionState(sessionId) {
67
+ return MunimFfmpeg.getSessionState(sessionId);
68
+ }
49
69
  export function getFFmpegVersion() {
50
70
  return MunimFfmpeg.ffmpegVersion;
51
71
  }
@@ -9,6 +9,12 @@ export type FFmpegSessionResult = {
9
9
  output: string;
10
10
  failStackTrace?: string;
11
11
  };
12
+ /**
13
+ * Where a session is in its life. `queued` sessions wait for the one running
14
+ * ahead of them (FFmpeg runs one execution at a time); `unknown` means the id
15
+ * was never issued or its record has been dropped.
16
+ */
17
+ export type FFmpegSessionState = 'queued' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled' | 'unknown';
12
18
  export type FFmpegLogCallback = (message: string) => void;
13
19
  export type FFmpegSessionCreatedCallback = (sessionId: number) => void;
14
20
  export type FFmpegStatisticsCallback = (timeMs: number, sizeBytes: number, bitrateKbits: number, speed: number, videoFrameNumber: number, fps: number, quality: number) => void;
@@ -22,4 +28,13 @@ export interface MunimFfmpeg extends HybridObject<{
22
28
  getMediaInformation(path: string): Promise<string>;
23
29
  cancel(sessionId?: number): void;
24
30
  cancelAll(): void;
31
+ /**
32
+ * Pauses an `execute()` session, running or still queued. Input stops being
33
+ * read and the pipeline idles; output files stay open. Returns false if the
34
+ * session is unknown, finished, or a probe (probes cannot be paused).
35
+ */
36
+ pause(sessionId: number): boolean;
37
+ /** Resumes a paused session. Returns false if it was not paused. */
38
+ resume(sessionId: number): boolean;
39
+ getSessionState(sessionId: number): FFmpegSessionState;
25
40
  }
@@ -0,0 +1,73 @@
1
+ ///
2
+ /// JFFmpegSessionState.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright Β© Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #include <fbjni/fbjni.h>
11
+ #include "FFmpegSessionState.hpp"
12
+
13
+ namespace margelo::nitro::munimffmpeg {
14
+
15
+ using namespace facebook;
16
+
17
+ /**
18
+ * The C++ JNI bridge between the C++ enum "FFmpegSessionState" and the Kotlin enum "FFmpegSessionState".
19
+ */
20
+ struct JFFmpegSessionState final: public jni::JavaClass<JFFmpegSessionState> {
21
+ public:
22
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/munimffmpeg/FFmpegSessionState;";
23
+
24
+ public:
25
+ /**
26
+ * Convert this Java/Kotlin-based enum to the C++ enum FFmpegSessionState.
27
+ */
28
+ [[maybe_unused]]
29
+ [[nodiscard]]
30
+ FFmpegSessionState toCpp() const {
31
+ static const auto clazz = javaClassStatic();
32
+ static const auto fieldOrdinal = clazz->getField<int>("value");
33
+ int ordinal = this->getFieldValue(fieldOrdinal);
34
+ return static_cast<FFmpegSessionState>(ordinal);
35
+ }
36
+
37
+ public:
38
+ /**
39
+ * Create a Java/Kotlin-based enum with the given C++ enum's value.
40
+ */
41
+ [[maybe_unused]]
42
+ static jni::alias_ref<JFFmpegSessionState> fromCpp(FFmpegSessionState value) {
43
+ static const auto clazz = javaClassStatic();
44
+ switch (value) {
45
+ case FFmpegSessionState::QUEUED:
46
+ static const auto fieldQUEUED = clazz->getStaticField<JFFmpegSessionState>("QUEUED");
47
+ return clazz->getStaticFieldValue(fieldQUEUED);
48
+ case FFmpegSessionState::RUNNING:
49
+ static const auto fieldRUNNING = clazz->getStaticField<JFFmpegSessionState>("RUNNING");
50
+ return clazz->getStaticFieldValue(fieldRUNNING);
51
+ case FFmpegSessionState::PAUSED:
52
+ static const auto fieldPAUSED = clazz->getStaticField<JFFmpegSessionState>("PAUSED");
53
+ return clazz->getStaticFieldValue(fieldPAUSED);
54
+ case FFmpegSessionState::COMPLETED:
55
+ static const auto fieldCOMPLETED = clazz->getStaticField<JFFmpegSessionState>("COMPLETED");
56
+ return clazz->getStaticFieldValue(fieldCOMPLETED);
57
+ case FFmpegSessionState::FAILED:
58
+ static const auto fieldFAILED = clazz->getStaticField<JFFmpegSessionState>("FAILED");
59
+ return clazz->getStaticFieldValue(fieldFAILED);
60
+ case FFmpegSessionState::CANCELLED:
61
+ static const auto fieldCANCELLED = clazz->getStaticField<JFFmpegSessionState>("CANCELLED");
62
+ return clazz->getStaticFieldValue(fieldCANCELLED);
63
+ case FFmpegSessionState::UNKNOWN:
64
+ static const auto fieldUNKNOWN = clazz->getStaticField<JFFmpegSessionState>("UNKNOWN");
65
+ return clazz->getStaticFieldValue(fieldUNKNOWN);
66
+ default:
67
+ std::string stringValue = std::to_string(static_cast<int>(value));
68
+ throw std::invalid_argument("Invalid enum value (" + stringValue + "!");
69
+ }
70
+ }
71
+ };
72
+
73
+ } // namespace margelo::nitro::munimffmpeg
@@ -9,6 +9,8 @@
9
9
 
10
10
  // Forward declaration of `FFmpegSessionResult` to properly resolve imports.
11
11
  namespace margelo::nitro::munimffmpeg { struct FFmpegSessionResult; }
12
+ // Forward declaration of `FFmpegSessionState` to properly resolve imports.
13
+ namespace margelo::nitro::munimffmpeg { enum class FFmpegSessionState; }
12
14
 
13
15
  #include <string>
14
16
  #include "FFmpegSessionResult.hpp"
@@ -16,6 +18,8 @@ namespace margelo::nitro::munimffmpeg { struct FFmpegSessionResult; }
16
18
  #include <NitroModules/JPromise.hpp>
17
19
  #include "JFFmpegSessionResult.hpp"
18
20
  #include <optional>
21
+ #include "FFmpegSessionState.hpp"
22
+ #include "JFFmpegSessionState.hpp"
19
23
  #include <vector>
20
24
  #include <functional>
21
25
  #include "JFunc_void_std__string.hpp"
@@ -134,5 +138,20 @@ namespace margelo::nitro::munimffmpeg {
134
138
  static const auto method = _javaPart->javaClassStatic()->getMethod<void()>("cancelAll");
135
139
  method(_javaPart);
136
140
  }
141
+ bool JHybridMunimFfmpegSpec::pause(double sessionId) {
142
+ static const auto method = _javaPart->javaClassStatic()->getMethod<jboolean(double /* sessionId */)>("pause");
143
+ auto __result = method(_javaPart, sessionId);
144
+ return static_cast<bool>(__result);
145
+ }
146
+ bool JHybridMunimFfmpegSpec::resume(double sessionId) {
147
+ static const auto method = _javaPart->javaClassStatic()->getMethod<jboolean(double /* sessionId */)>("resume");
148
+ auto __result = method(_javaPart, sessionId);
149
+ return static_cast<bool>(__result);
150
+ }
151
+ FFmpegSessionState JHybridMunimFfmpegSpec::getSessionState(double sessionId) {
152
+ static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JFFmpegSessionState>(double /* sessionId */)>("getSessionState");
153
+ auto __result = method(_javaPart, sessionId);
154
+ return __result->toCpp();
155
+ }
137
156
 
138
157
  } // namespace margelo::nitro::munimffmpeg
@@ -59,6 +59,9 @@ namespace margelo::nitro::munimffmpeg {
59
59
  std::shared_ptr<Promise<std::string>> getMediaInformation(const std::string& path) override;
60
60
  void cancel(std::optional<double> sessionId) override;
61
61
  void cancelAll() override;
62
+ bool pause(double sessionId) override;
63
+ bool resume(double sessionId) override;
64
+ FFmpegSessionState getSessionState(double sessionId) override;
62
65
 
63
66
  private:
64
67
  jni::global_ref<JHybridMunimFfmpegSpec::JavaPart> _javaPart;
@@ -0,0 +1,28 @@
1
+ ///
2
+ /// FFmpegSessionState.kt
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright Β© Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ package com.margelo.nitro.munimffmpeg
9
+
10
+ import androidx.annotation.Keep
11
+ import com.facebook.proguard.annotations.DoNotStrip
12
+
13
+ /**
14
+ * Represents the JavaScript enum/union "FFmpegSessionState".
15
+ */
16
+ @DoNotStrip
17
+ @Keep
18
+ enum class FFmpegSessionState(@DoNotStrip @Keep val value: Int) {
19
+ QUEUED(0),
20
+ RUNNING(1),
21
+ PAUSED(2),
22
+ COMPLETED(3),
23
+ FAILED(4),
24
+ CANCELLED(5),
25
+ UNKNOWN(6);
26
+
27
+ companion object
28
+ }
@@ -61,6 +61,18 @@ abstract class HybridMunimFfmpegSpec: HybridObject() {
61
61
  @DoNotStrip
62
62
  @Keep
63
63
  abstract fun cancelAll(): Unit
64
+
65
+ @DoNotStrip
66
+ @Keep
67
+ abstract fun pause(sessionId: Double): Boolean
68
+
69
+ @DoNotStrip
70
+ @Keep
71
+ abstract fun resume(sessionId: Double): Boolean
72
+
73
+ @DoNotStrip
74
+ @Keep
75
+ abstract fun getSessionState(sessionId: Double): FFmpegSessionState
64
76
 
65
77
  // Default implementation of `HybridObject.toString()`
66
78
  override fun toString(): String {
@@ -10,6 +10,8 @@
10
10
  // Forward declarations of C++ defined types
11
11
  // Forward declaration of `FFmpegSessionResult` to properly resolve imports.
12
12
  namespace margelo::nitro::munimffmpeg { struct FFmpegSessionResult; }
13
+ // Forward declaration of `FFmpegSessionState` to properly resolve imports.
14
+ namespace margelo::nitro::munimffmpeg { enum class FFmpegSessionState; }
13
15
  // Forward declaration of `HybridMunimFfmpegSpec` to properly resolve imports.
14
16
  namespace margelo::nitro::munimffmpeg { class HybridMunimFfmpegSpec; }
15
17
 
@@ -19,6 +21,7 @@ namespace NitroMunimFfmpeg { class HybridMunimFfmpegSpec_cxx; }
19
21
 
20
22
  // Include C++ defined types
21
23
  #include "FFmpegSessionResult.hpp"
24
+ #include "FFmpegSessionState.hpp"
22
25
  #include "HybridMunimFfmpegSpec.hpp"
23
26
  #include <NitroModules/Promise.hpp>
24
27
  #include <NitroModules/PromiseHolder.hpp>
@@ -294,5 +297,23 @@ namespace margelo::nitro::munimffmpeg::bridge::swift {
294
297
  inline Result_void_ create_Result_void_(const std::exception_ptr& error) noexcept {
295
298
  return Result<void>::withError(error);
296
299
  }
300
+
301
+ // pragma MARK: Result<bool>
302
+ using Result_bool_ = Result<bool>;
303
+ inline Result_bool_ create_Result_bool_(bool value) noexcept {
304
+ return Result<bool>::withValue(std::move(value));
305
+ }
306
+ inline Result_bool_ create_Result_bool_(const std::exception_ptr& error) noexcept {
307
+ return Result<bool>::withError(error);
308
+ }
309
+
310
+ // pragma MARK: Result<FFmpegSessionState>
311
+ using Result_FFmpegSessionState_ = Result<FFmpegSessionState>;
312
+ inline Result_FFmpegSessionState_ create_Result_FFmpegSessionState_(FFmpegSessionState value) noexcept {
313
+ return Result<FFmpegSessionState>::withValue(std::move(value));
314
+ }
315
+ inline Result_FFmpegSessionState_ create_Result_FFmpegSessionState_(const std::exception_ptr& error) noexcept {
316
+ return Result<FFmpegSessionState>::withError(error);
317
+ }
297
318
 
298
319
  } // namespace margelo::nitro::munimffmpeg::bridge::swift
@@ -10,11 +10,14 @@
10
10
  // Forward declarations of C++ defined types
11
11
  // Forward declaration of `FFmpegSessionResult` to properly resolve imports.
12
12
  namespace margelo::nitro::munimffmpeg { struct FFmpegSessionResult; }
13
+ // Forward declaration of `FFmpegSessionState` to properly resolve imports.
14
+ namespace margelo::nitro::munimffmpeg { enum class FFmpegSessionState; }
13
15
  // Forward declaration of `HybridMunimFfmpegSpec` to properly resolve imports.
14
16
  namespace margelo::nitro::munimffmpeg { class HybridMunimFfmpegSpec; }
15
17
 
16
18
  // Include C++ defined types
17
19
  #include "FFmpegSessionResult.hpp"
20
+ #include "FFmpegSessionState.hpp"
18
21
  #include "HybridMunimFfmpegSpec.hpp"
19
22
  #include <NitroModules/Promise.hpp>
20
23
  #include <NitroModules/Result.hpp>
@@ -14,6 +14,8 @@ namespace NitroMunimFfmpeg { class HybridMunimFfmpegSpec_cxx; }
14
14
 
15
15
  // Forward declaration of `FFmpegSessionResult` to properly resolve imports.
16
16
  namespace margelo::nitro::munimffmpeg { struct FFmpegSessionResult; }
17
+ // Forward declaration of `FFmpegSessionState` to properly resolve imports.
18
+ namespace margelo::nitro::munimffmpeg { enum class FFmpegSessionState; }
17
19
 
18
20
  #include <string>
19
21
  #include "FFmpegSessionResult.hpp"
@@ -21,6 +23,7 @@ namespace margelo::nitro::munimffmpeg { struct FFmpegSessionResult; }
21
23
  #include <optional>
22
24
  #include <vector>
23
25
  #include <functional>
26
+ #include "FFmpegSessionState.hpp"
24
27
 
25
28
  #include "NitroMunimFfmpeg-Swift-Cxx-Umbrella.hpp"
26
29
 
@@ -111,6 +114,30 @@ namespace margelo::nitro::munimffmpeg {
111
114
  std::rethrow_exception(__result.error());
112
115
  }
113
116
  }
117
+ inline bool pause(double sessionId) override {
118
+ auto __result = _swiftPart.pause(std::forward<decltype(sessionId)>(sessionId));
119
+ if (__result.hasError()) [[unlikely]] {
120
+ std::rethrow_exception(__result.error());
121
+ }
122
+ auto __value = std::move(__result.value());
123
+ return __value;
124
+ }
125
+ inline bool resume(double sessionId) override {
126
+ auto __result = _swiftPart.resume(std::forward<decltype(sessionId)>(sessionId));
127
+ if (__result.hasError()) [[unlikely]] {
128
+ std::rethrow_exception(__result.error());
129
+ }
130
+ auto __value = std::move(__result.value());
131
+ return __value;
132
+ }
133
+ inline FFmpegSessionState getSessionState(double sessionId) override {
134
+ auto __result = _swiftPart.getSessionState(std::forward<decltype(sessionId)>(sessionId));
135
+ if (__result.hasError()) [[unlikely]] {
136
+ std::rethrow_exception(__result.error());
137
+ }
138
+ auto __value = std::move(__result.value());
139
+ return __value;
140
+ }
114
141
 
115
142
  private:
116
143
  NitroMunimFfmpeg::HybridMunimFfmpegSpec_cxx _swiftPart;
@@ -0,0 +1,60 @@
1
+ ///
2
+ /// FFmpegSessionState.swift
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright Β© Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ /**
9
+ * Represents the JS union `FFmpegSessionState`, backed by a C++ enum.
10
+ */
11
+ public typealias FFmpegSessionState = margelo.nitro.munimffmpeg.FFmpegSessionState
12
+
13
+ public extension FFmpegSessionState {
14
+ /**
15
+ * Get a FFmpegSessionState for the given String value, or
16
+ * return `nil` if the given value was invalid/unknown.
17
+ */
18
+ init?(fromString string: String) {
19
+ switch string {
20
+ case "queued":
21
+ self = .queued
22
+ case "running":
23
+ self = .running
24
+ case "paused":
25
+ self = .paused
26
+ case "completed":
27
+ self = .completed
28
+ case "failed":
29
+ self = .failed
30
+ case "cancelled":
31
+ self = .cancelled
32
+ case "unknown":
33
+ self = .unknown
34
+ default:
35
+ return nil
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Get the String value this FFmpegSessionState represents.
41
+ */
42
+ var stringValue: String {
43
+ switch self {
44
+ case .queued:
45
+ return "queued"
46
+ case .running:
47
+ return "running"
48
+ case .paused:
49
+ return "paused"
50
+ case .completed:
51
+ return "completed"
52
+ case .failed:
53
+ return "failed"
54
+ case .cancelled:
55
+ return "cancelled"
56
+ case .unknown:
57
+ return "unknown"
58
+ }
59
+ }
60
+ }
@@ -18,6 +18,9 @@ public protocol HybridMunimFfmpegSpec_protocol: HybridObject {
18
18
  func getMediaInformation(path: String) throws -> Promise<String>
19
19
  func cancel(sessionId: Double?) throws -> Void
20
20
  func cancelAll() throws -> Void
21
+ func pause(sessionId: Double) throws -> Bool
22
+ func resume(sessionId: Double) throws -> Bool
23
+ func getSessionState(sessionId: Double) throws -> FFmpegSessionState
21
24
  }
22
25
 
23
26
  public extension HybridMunimFfmpegSpec_protocol {
@@ -274,4 +274,40 @@ open class HybridMunimFfmpegSpec_cxx {
274
274
  return bridge.create_Result_void_(__exceptionPtr)
275
275
  }
276
276
  }
277
+
278
+ @inline(__always)
279
+ public final func pause(sessionId: Double) -> bridge.Result_bool_ {
280
+ do {
281
+ let __result = try self.__implementation.pause(sessionId: sessionId)
282
+ let __resultCpp = __result
283
+ return bridge.create_Result_bool_(__resultCpp)
284
+ } catch (let __error) {
285
+ let __exceptionPtr = __error.toCpp()
286
+ return bridge.create_Result_bool_(__exceptionPtr)
287
+ }
288
+ }
289
+
290
+ @inline(__always)
291
+ public final func resume(sessionId: Double) -> bridge.Result_bool_ {
292
+ do {
293
+ let __result = try self.__implementation.resume(sessionId: sessionId)
294
+ let __resultCpp = __result
295
+ return bridge.create_Result_bool_(__resultCpp)
296
+ } catch (let __error) {
297
+ let __exceptionPtr = __error.toCpp()
298
+ return bridge.create_Result_bool_(__exceptionPtr)
299
+ }
300
+ }
301
+
302
+ @inline(__always)
303
+ public final func getSessionState(sessionId: Double) -> bridge.Result_FFmpegSessionState_ {
304
+ do {
305
+ let __result = try self.__implementation.getSessionState(sessionId: sessionId)
306
+ let __resultCpp = __result
307
+ return bridge.create_Result_FFmpegSessionState_(__resultCpp)
308
+ } catch (let __error) {
309
+ let __exceptionPtr = __error.toCpp()
310
+ return bridge.create_Result_FFmpegSessionState_(__exceptionPtr)
311
+ }
312
+ }
277
313
  }
@@ -0,0 +1,96 @@
1
+ ///
2
+ /// FFmpegSessionState.hpp
3
+ /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
+ /// https://github.com/mrousavy/nitro
5
+ /// Copyright Β© Marc Rousavy @ Margelo
6
+ ///
7
+
8
+ #pragma once
9
+
10
+ #if __has_include(<NitroModules/NitroHash.hpp>)
11
+ #include <NitroModules/NitroHash.hpp>
12
+ #else
13
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
14
+ #endif
15
+ #if __has_include(<NitroModules/JSIConverter.hpp>)
16
+ #include <NitroModules/JSIConverter.hpp>
17
+ #else
18
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
19
+ #endif
20
+ #if __has_include(<NitroModules/NitroDefines.hpp>)
21
+ #include <NitroModules/NitroDefines.hpp>
22
+ #else
23
+ #error NitroModules cannot be found! Are you sure you installed NitroModules properly?
24
+ #endif
25
+
26
+ namespace margelo::nitro::munimffmpeg {
27
+
28
+ /**
29
+ * An enum which can be represented as a JavaScript union (FFmpegSessionState).
30
+ */
31
+ enum class FFmpegSessionState {
32
+ QUEUED SWIFT_NAME(queued) = 0,
33
+ RUNNING SWIFT_NAME(running) = 1,
34
+ PAUSED SWIFT_NAME(paused) = 2,
35
+ COMPLETED SWIFT_NAME(completed) = 3,
36
+ FAILED SWIFT_NAME(failed) = 4,
37
+ CANCELLED SWIFT_NAME(cancelled) = 5,
38
+ UNKNOWN SWIFT_NAME(unknown) = 6,
39
+ } CLOSED_ENUM;
40
+
41
+ } // namespace margelo::nitro::munimffmpeg
42
+
43
+ namespace margelo::nitro {
44
+
45
+ // C++ FFmpegSessionState <> JS FFmpegSessionState (union)
46
+ template <>
47
+ struct JSIConverter<margelo::nitro::munimffmpeg::FFmpegSessionState> final {
48
+ static inline margelo::nitro::munimffmpeg::FFmpegSessionState fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
49
+ std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
50
+ switch (hashString(unionValue.c_str(), unionValue.size())) {
51
+ case hashString("queued"): return margelo::nitro::munimffmpeg::FFmpegSessionState::QUEUED;
52
+ case hashString("running"): return margelo::nitro::munimffmpeg::FFmpegSessionState::RUNNING;
53
+ case hashString("paused"): return margelo::nitro::munimffmpeg::FFmpegSessionState::PAUSED;
54
+ case hashString("completed"): return margelo::nitro::munimffmpeg::FFmpegSessionState::COMPLETED;
55
+ case hashString("failed"): return margelo::nitro::munimffmpeg::FFmpegSessionState::FAILED;
56
+ case hashString("cancelled"): return margelo::nitro::munimffmpeg::FFmpegSessionState::CANCELLED;
57
+ case hashString("unknown"): return margelo::nitro::munimffmpeg::FFmpegSessionState::UNKNOWN;
58
+ default: [[unlikely]]
59
+ throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum FFmpegSessionState - invalid value!");
60
+ }
61
+ }
62
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::munimffmpeg::FFmpegSessionState arg) {
63
+ switch (arg) {
64
+ case margelo::nitro::munimffmpeg::FFmpegSessionState::QUEUED: return JSIConverter<std::string>::toJSI(runtime, "queued");
65
+ case margelo::nitro::munimffmpeg::FFmpegSessionState::RUNNING: return JSIConverter<std::string>::toJSI(runtime, "running");
66
+ case margelo::nitro::munimffmpeg::FFmpegSessionState::PAUSED: return JSIConverter<std::string>::toJSI(runtime, "paused");
67
+ case margelo::nitro::munimffmpeg::FFmpegSessionState::COMPLETED: return JSIConverter<std::string>::toJSI(runtime, "completed");
68
+ case margelo::nitro::munimffmpeg::FFmpegSessionState::FAILED: return JSIConverter<std::string>::toJSI(runtime, "failed");
69
+ case margelo::nitro::munimffmpeg::FFmpegSessionState::CANCELLED: return JSIConverter<std::string>::toJSI(runtime, "cancelled");
70
+ case margelo::nitro::munimffmpeg::FFmpegSessionState::UNKNOWN: return JSIConverter<std::string>::toJSI(runtime, "unknown");
71
+ default: [[unlikely]]
72
+ throw std::invalid_argument("Cannot convert FFmpegSessionState to JS - invalid value: "
73
+ + std::to_string(static_cast<int>(arg)) + "!");
74
+ }
75
+ }
76
+ static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
77
+ if (!value.isString()) {
78
+ return false;
79
+ }
80
+ std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, value);
81
+ switch (hashString(unionValue.c_str(), unionValue.size())) {
82
+ case hashString("queued"):
83
+ case hashString("running"):
84
+ case hashString("paused"):
85
+ case hashString("completed"):
86
+ case hashString("failed"):
87
+ case hashString("cancelled"):
88
+ case hashString("unknown"):
89
+ return true;
90
+ default:
91
+ return false;
92
+ }
93
+ }
94
+ };
95
+
96
+ } // namespace margelo::nitro
@@ -20,6 +20,9 @@ namespace margelo::nitro::munimffmpeg {
20
20
  prototype.registerHybridMethod("getMediaInformation", &HybridMunimFfmpegSpec::getMediaInformation);
21
21
  prototype.registerHybridMethod("cancel", &HybridMunimFfmpegSpec::cancel);
22
22
  prototype.registerHybridMethod("cancelAll", &HybridMunimFfmpegSpec::cancelAll);
23
+ prototype.registerHybridMethod("pause", &HybridMunimFfmpegSpec::pause);
24
+ prototype.registerHybridMethod("resume", &HybridMunimFfmpegSpec::resume);
25
+ prototype.registerHybridMethod("getSessionState", &HybridMunimFfmpegSpec::getSessionState);
23
26
  });
24
27
  }
25
28
 
@@ -15,6 +15,8 @@
15
15
 
16
16
  // Forward declaration of `FFmpegSessionResult` to properly resolve imports.
17
17
  namespace margelo::nitro::munimffmpeg { struct FFmpegSessionResult; }
18
+ // Forward declaration of `FFmpegSessionState` to properly resolve imports.
19
+ namespace margelo::nitro::munimffmpeg { enum class FFmpegSessionState; }
18
20
 
19
21
  #include <string>
20
22
  #include "FFmpegSessionResult.hpp"
@@ -22,6 +24,7 @@ namespace margelo::nitro::munimffmpeg { struct FFmpegSessionResult; }
22
24
  #include <vector>
23
25
  #include <functional>
24
26
  #include <optional>
27
+ #include "FFmpegSessionState.hpp"
25
28
 
26
29
  namespace margelo::nitro::munimffmpeg {
27
30
 
@@ -59,6 +62,9 @@ namespace margelo::nitro::munimffmpeg {
59
62
  virtual std::shared_ptr<Promise<std::string>> getMediaInformation(const std::string& path) = 0;
60
63
  virtual void cancel(std::optional<double> sessionId) = 0;
61
64
  virtual void cancelAll() = 0;
65
+ virtual bool pause(double sessionId) = 0;
66
+ virtual bool resume(double sessionId) = 0;
67
+ virtual FFmpegSessionState getSessionState(double sessionId) = 0;
62
68
 
63
69
  protected:
64
70
  // Hybrid Setup
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "munim-ffmpeg",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Fast FFmpeg and FFprobe for Expo and React Native, powered by Nitro Modules",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "archive": "munim-ffmpeg-binaries.tar.gz",
3
- "sha256": "6d709ca6ddf428743c29d54325cca34c3335d771076690472bb036c2d849e64f",
3
+ "sha256": "7f5d42fff137ddf710360c11667c8113fdf1c241b6cbd0e4b5a2e1ba11b152f2",
4
4
  "ffmpeg": "9.0.2"
5
5
  }
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ import type {
3
3
  FFmpegLogCallback,
4
4
  FFmpegSessionResult,
5
5
  FFmpegSessionCreatedCallback,
6
+ FFmpegSessionState,
6
7
  FFmpegStatisticsCallback,
7
8
  MunimFfmpeg as MunimFfmpegSpec,
8
9
  } from './specs/MunimFfmpeg.nitro'
@@ -14,6 +15,7 @@ export type {
14
15
  FFmpegLogCallback,
15
16
  FFmpegSessionResult,
16
17
  FFmpegSessionCreatedCallback,
18
+ FFmpegSessionState,
17
19
  FFmpegStatisticsCallback,
18
20
  MunimFfmpegSpec,
19
21
  }
@@ -177,6 +179,29 @@ export function cancelAll(): void {
177
179
  MunimFfmpeg.cancelAll()
178
180
  }
179
181
 
182
+ /**
183
+ * Pauses a session started with `execute()`, whether it is running or still
184
+ * queued. FFmpeg stops reading its inputs and the rest of the pipeline idles
185
+ * once it has drained, so a paused session costs no CPU. Output files stay
186
+ * open, and `resume()` continues from exactly where it stopped: nothing is
187
+ * re-encoded or skipped, and the `execute()` promise settles once, when the
188
+ * work finishes. `cancel()` still works while paused.
189
+ *
190
+ * Returns false if the session is unknown, has finished, or is a probe.
191
+ */
192
+ export function pause(sessionId: number): boolean {
193
+ return MunimFfmpeg.pause(sessionId)
194
+ }
195
+
196
+ /** Resumes a paused session. Returns false if it was not paused. */
197
+ export function resume(sessionId: number): boolean {
198
+ return MunimFfmpeg.resume(sessionId)
199
+ }
200
+
201
+ export function getSessionState(sessionId: number): FFmpegSessionState {
202
+ return MunimFfmpeg.getSessionState(sessionId)
203
+ }
204
+
180
205
  export function getFFmpegVersion(): string {
181
206
  return MunimFfmpeg.ffmpegVersion
182
207
  }
@@ -11,6 +11,20 @@ export type FFmpegSessionResult = {
11
11
  failStackTrace?: string
12
12
  }
13
13
 
14
+ /**
15
+ * Where a session is in its life. `queued` sessions wait for the one running
16
+ * ahead of them (FFmpeg runs one execution at a time); `unknown` means the id
17
+ * was never issued or its record has been dropped.
18
+ */
19
+ export type FFmpegSessionState =
20
+ | 'queued'
21
+ | 'running'
22
+ | 'paused'
23
+ | 'completed'
24
+ | 'failed'
25
+ | 'cancelled'
26
+ | 'unknown'
27
+
14
28
  export type FFmpegLogCallback = (message: string) => void
15
29
 
16
30
  export type FFmpegSessionCreatedCallback = (sessionId: number) => void
@@ -49,4 +63,16 @@ export interface MunimFfmpeg extends HybridObject<{
49
63
  cancel(sessionId?: number): void
50
64
 
51
65
  cancelAll(): void
66
+
67
+ /**
68
+ * Pauses an `execute()` session, running or still queued. Input stops being
69
+ * read and the pipeline idles; output files stay open. Returns false if the
70
+ * session is unknown, finished, or a probe (probes cannot be paused).
71
+ */
72
+ pause(sessionId: number): boolean
73
+
74
+ /** Resumes a paused session. Returns false if it was not paused. */
75
+ resume(sessionId: number): boolean
76
+
77
+ getSessionState(sessionId: number): FFmpegSessionState
52
78
  }