munim-ffmpeg 0.1.1 โ 0.3.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.
- package/NitroMunimFfmpeg.podspec +15 -9
- package/README.md +180 -35
- package/android/build.gradle +2 -2
- package/android/src/main/java/com/margelo/nitro/munimffmpeg/FFmpegNative.kt +86 -0
- package/android/src/main/java/com/margelo/nitro/munimffmpeg/HybridMunimFfmpeg.kt +100 -63
- package/app.plugin.js +58 -2
- package/ios/Bridge.h +4 -1
- package/ios/HybridMunimFfmpeg.swift +176 -90
- package/ios/munim_ffmpeg_core.h +58 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.js +73 -3
- package/package.json +36 -38
- package/scripts/binaries.json +5 -0
- package/scripts/fetch-binaries.mjs +115 -0
- package/src/index.ts +94 -3
- package/scripts/patch-ffmpegkit-level.rb +0 -45
package/NitroMunimFfmpeg.podspec
CHANGED
|
@@ -18,24 +18,30 @@ Pod::Spec.new do |s|
|
|
|
18
18
|
"ios/**/*.{swift}",
|
|
19
19
|
# Autolinking/Registration (Objective-C++)
|
|
20
20
|
"ios/**/*.{m,mm}",
|
|
21
|
+
# C core that drives FFmpeg's in-process command-line tools
|
|
22
|
+
"ios/*.h",
|
|
21
23
|
# Implementation (C++ objects)
|
|
22
24
|
"cpp/**/*.{hpp,cpp}",
|
|
23
25
|
]
|
|
24
26
|
|
|
27
|
+
# FFmpeg 9 and the in-process fftools core. Downloaded by
|
|
28
|
+
# scripts/fetch-binaries.mjs on install, or built by scripts/ffmpeg/build-all.sh.
|
|
29
|
+
s.vendored_frameworks = "ios/MunimFFmpeg.xcframework"
|
|
30
|
+
s.pod_target_xcconfig = {
|
|
31
|
+
"HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/ios\"",
|
|
32
|
+
}
|
|
33
|
+
s.frameworks = "AudioToolbox", "VideoToolbox", "CoreMedia", "AVFoundation", "CoreVideo", "Security"
|
|
34
|
+
s.libraries = "bz2", "z", "iconv", "c++"
|
|
35
|
+
|
|
36
|
+
# Must be public so CocoaPods puts it in the module umbrella, which is how the
|
|
37
|
+
# Swift implementation sees the C core.
|
|
38
|
+
s.public_header_files = "ios/munim_ffmpeg_core.h"
|
|
39
|
+
|
|
25
40
|
load 'nitrogen/generated/ios/NitroMunimFfmpeg+autolinking.rb'
|
|
26
41
|
add_nitrogen_files(s)
|
|
27
42
|
|
|
28
43
|
s.dependency 'React-jsi'
|
|
29
44
|
s.dependency 'React-callinvoker'
|
|
30
|
-
s.dependency 'ffmpeg-kit-ios-https-alt', '6.0'
|
|
31
|
-
|
|
32
|
-
# ffmpeg-kit 6.0 declares negative Level values with an unsigned backing type.
|
|
33
|
-
# Xcode 26 rejects that header when Nitro enables Swift C++ interoperability.
|
|
34
|
-
s.script_phase = {
|
|
35
|
-
:name => 'Patch FFmpegKit Level enum for Xcode 26',
|
|
36
|
-
:script => '"${RUBY_EXECUTABLE:-/usr/bin/ruby}" "${PODS_TARGET_SRCROOT}/scripts/patch-ffmpegkit-level.rb"',
|
|
37
|
-
:execution_position => :before_compile,
|
|
38
|
-
}
|
|
39
45
|
|
|
40
46
|
install_modules_dependencies(s)
|
|
41
47
|
end
|
package/README.md
CHANGED
|
@@ -61,19 +61,20 @@
|
|
|
61
61
|
|
|
62
62
|
**Designed for Expo development builds and bare React Native.** This package contains native code and cannot run in Expo Go.
|
|
63
63
|
|
|
64
|
-
> **Licensing note:** The JavaScript and Nitro bridge are Apache-2.0. The bundled
|
|
64
|
+
> **Licensing note:** The JavaScript, Swift, Kotlin, and Nitro bridge are Apache-2.0. The bundled FFmpeg is **LGPLv3** on both platforms โ it deliberately excludes x264, x265, and xvid, so your app does not inherit GPL obligations. See [Licensing](#licensing).
|
|
65
65
|
|
|
66
66
|
## Table of contents
|
|
67
67
|
|
|
68
68
|
- [๐ Documentation](#-documentation)
|
|
69
69
|
- [๐ Features](#-features)
|
|
70
70
|
- [Platform support matrix](#platform-support-matrix)
|
|
71
|
+
- [Bundled FFmpeg builds](#bundled-ffmpeg-builds)
|
|
71
72
|
- [๐ฆ Installation](#-installation)
|
|
72
73
|
- [Working with media paths](#working-with-media-paths)
|
|
73
74
|
- [โก Quick start](#-quick-start)
|
|
74
75
|
- [๐ง API reference](#-api-reference)
|
|
75
76
|
- [๐ Usage examples](#-usage-examples)
|
|
76
|
-
- [
|
|
77
|
+
- [Licensing](#licensing)
|
|
77
78
|
- [๐ Troubleshooting](#-troubleshooting)
|
|
78
79
|
- [Development](#development)
|
|
79
80
|
- [๐ Contributing](#-contributing)
|
|
@@ -91,7 +92,8 @@
|
|
|
91
92
|
|
|
92
93
|
### FFmpeg execution
|
|
93
94
|
|
|
94
|
-
- ๐ฌ **Argument-array commands:**
|
|
95
|
+
- ๐ฌ **Argument-array commands:** FFmpeg's own CLI code paths, without shell parsing or quoting
|
|
96
|
+
- ๐ **FFmpeg 9.0.1:** The current upstream release, identical on both platforms
|
|
95
97
|
- โก **Asynchronous sessions:** Keep the React Native thread responsive during native work
|
|
96
98
|
- ๐ **Live logs:** Receive FFmpeg output as it is produced
|
|
97
99
|
- ๐ **Encoding statistics:** Track time, size, bitrate, speed, frames, FPS, and quality
|
|
@@ -109,8 +111,9 @@
|
|
|
109
111
|
- ๐ฑ **iOS and Android:** Native implementations in Swift and Kotlin
|
|
110
112
|
- ๐งฌ **Nitro Modules:** Generated high-performance native bindings
|
|
111
113
|
- ๐ **Expo compatible:** Autolinking, config plugin, and an Expo development example
|
|
114
|
+
- ๐งช **Capability discovery:** Ask the bundled build which encoders and decoders it actually has
|
|
112
115
|
- ๐ฏ **TypeScript:** Complete public callback and result types
|
|
113
|
-
- ๐๏ธ **16 KB Android
|
|
116
|
+
- ๐๏ธ **16 KB Android pages:** Built with the alignment Google Play requires
|
|
114
117
|
|
|
115
118
|
## Platform support matrix
|
|
116
119
|
|
|
@@ -125,9 +128,54 @@
|
|
|
125
128
|
| Cancel one FFmpeg session | โ
| โ
| Pass the positive safe-integer ID received by `execute`'s `onSessionCreated`. The native dependency does not expose FFprobe cancellation. |
|
|
126
129
|
| Cancel all FFmpeg sessions | โ
| โ
| Use `cancelAll()` or call `cancel()` without an ID. |
|
|
127
130
|
| Expo Go | โ | โ | A native development build is required. |
|
|
128
|
-
|
|
|
131
|
+
| Capability discovery | โ
| โ
| `listEncoders()`, `listDecoders()`, and `pickEncoder()` report what the bundled build supports. |
|
|
132
|
+
| H.264 encoding | VideoToolbox | libx264 | The builds differ; use `pickEncoder(['libx264', 'h264_videotoolbox'])` instead of hard-coding an encoder. |
|
|
133
|
+
| Remote HTTP(S) inputs | โ
| โ
| Both builds link GnuTLS. Remote server behaviour still varies; prefer local files for predictable app workflows. |
|
|
129
134
|
|
|
130
|
-
Codec availability is determined by the native FFmpeg builds
|
|
135
|
+
Codec availability is determined by the native FFmpeg builds described in [Bundled FFmpeg builds](#bundled-ffmpeg-builds). Do not assume every FFmpeg codec or external library is present.
|
|
136
|
+
|
|
137
|
+
## Bundled FFmpeg builds
|
|
138
|
+
|
|
139
|
+
Both platforms run **FFmpeg 9.0.1**, built from [ffmpeg.org](https://www.ffmpeg.org/) by the scripts in [`scripts/ffmpeg/`](./scripts/ffmpeg). There is no FFmpegKit here: that project was retired in 2025 and pinned to FFmpeg 6.0.
|
|
140
|
+
|
|
141
|
+
| | iOS | Android |
|
|
142
|
+
| --- | --- | --- |
|
|
143
|
+
| FFmpeg | 9.0.1 | 9.0.1 |
|
|
144
|
+
| Architectures | arm64 device, arm64 + x86_64 simulator | arm64-v8a, armeabi-v7a, x86_64 |
|
|
145
|
+
| Hardware codecs | VideoToolbox, AudioToolbox | MediaCodec |
|
|
146
|
+
| TLS | SecureTransport | mbedTLS |
|
|
147
|
+
| Minimum | iOS 15.1 | API 24, 16 KB pages |
|
|
148
|
+
|
|
149
|
+
Linked libraries, identical on both: **LAME** (MP3), **Opus**, **libvpx** (VP8/VP9), **dav1d** (AV1 decoding), plus everything FFmpeg builds natively.
|
|
150
|
+
|
|
151
|
+
FFmpeg's own `ffmpeg` and `ffprobe` tools are compiled to run inside your app process, so the argument arrays you pass are handled by the real command-line code paths rather than a reimplementation.
|
|
152
|
+
|
|
153
|
+
### Encoders
|
|
154
|
+
|
|
155
|
+
Verified by running the example's device suite: iOS reports 186 encoders, Android 184. Everything FFmpeg builds natively (`aac`, `alac`, `flac`, `mpeg4`, `mjpeg`, `png`, `gif`, `pcm_*`, โฆ) is on both, as are `libmp3lame`, `libopus`, `libvpx`, and `libvpx-vp9`.
|
|
156
|
+
|
|
157
|
+
H.264 and HEVC come from the platform's hardware encoder, which is faster and smaller than bundling x264 โ and keeps the package LGPL:
|
|
158
|
+
|
|
159
|
+
| Encoder | iOS | Android |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| `h264_videotoolbox`, `hevc_videotoolbox`, `prores_videotoolbox` | โ
| โ |
|
|
162
|
+
| `h264_mediacodec`, `hevc_mediacodec`, `vp8_mediacodec`, `vp9_mediacodec` | โ | โ
|
|
|
163
|
+
| `aac_at`, `alac_at` (AudioToolbox) | โ
| โ |
|
|
164
|
+
|
|
165
|
+
Resolve the name at runtime instead of branching on `Platform.OS`:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { execute, pickEncoder } from 'munim-ffmpeg'
|
|
169
|
+
|
|
170
|
+
const h264 = await pickEncoder(['h264_videotoolbox', 'h264_mediacodec'])
|
|
171
|
+
if (!h264) throw new Error('No H.264 encoder in this build')
|
|
172
|
+
|
|
173
|
+
await execute(['-y', '-i', inputPath, '-c:v', h264, outputPath])
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Two things to know about hardware encoders: they want NV12 input on Android (`-pix_fmt nv12`) and planar YUV on iOS, and they reject very small frames โ 176ร144 is the smallest size that works everywhere.
|
|
177
|
+
|
|
178
|
+
Decoding is uniform: H.264, HEVC, VP8/VP9, AV1, MPEG-4, MP3, AAC, Vorbis, Opus, FLAC and the usual containers, on both platforms. Both link TLS, so `https://` inputs work.
|
|
131
179
|
|
|
132
180
|
## ๐ฆ Installation
|
|
133
181
|
|
|
@@ -147,7 +195,7 @@ pod install
|
|
|
147
195
|
cd ..
|
|
148
196
|
```
|
|
149
197
|
|
|
150
|
-
|
|
198
|
+
FFmpeg and React Native both provide `libc++_shared.so`. Resolve that duplicate in the Android application module:
|
|
151
199
|
|
|
152
200
|
```groovy
|
|
153
201
|
android {
|
|
@@ -175,7 +223,7 @@ The package includes an Expo config plugin. If your project manages its plugin l
|
|
|
175
223
|
}
|
|
176
224
|
```
|
|
177
225
|
|
|
178
|
-
The plugin also configures Android to select one shared C++ runtime when React Native and
|
|
226
|
+
The plugin also configures Android to select one shared C++ runtime when React Native and FFmpeg contribute the same `libc++_shared.so` path.
|
|
179
227
|
|
|
180
228
|
Create a native development build after installation:
|
|
181
229
|
|
|
@@ -190,6 +238,25 @@ You can also create an [EAS development build](https://docs.expo.dev/develop/dev
|
|
|
190
238
|
|
|
191
239
|
> **Important:** `munim-ffmpeg` cannot run in Expo Go because Expo Go does not include this package's native libraries.
|
|
192
240
|
|
|
241
|
+
### Native binaries
|
|
242
|
+
|
|
243
|
+
The FFmpeg libraries are around 200 MB across all six architectures, which does not belong in an npm tarball, so they are downloaded from the matching GitHub release when the package installs and verified against the checksum in `scripts/binaries.json`.
|
|
244
|
+
|
|
245
|
+
If your environment blocks install scripts (`npm install --ignore-scripts`), fetch them explicitly:
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
npx munim-ffmpeg-fetch-binaries
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Behind a proxy or an air-gapped mirror, point the fetcher somewhere else:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
MUNIM_FFMPEG_BINARIES_URL=https://internal.example.com/munim-ffmpeg-binaries.tar.gz \
|
|
255
|
+
npx munim-ffmpeg-fetch-binaries
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Or build them yourself โ see [`scripts/ffmpeg/README.md`](./scripts/ffmpeg/README.md).
|
|
259
|
+
|
|
193
260
|
### Requirements
|
|
194
261
|
|
|
195
262
|
- React Native with the New Architecture enabled
|
|
@@ -205,7 +272,7 @@ No camera, microphone, photo-library, or storage permission is added automatical
|
|
|
205
272
|
FFmpeg runs natively and needs a path or URI the native process can access.
|
|
206
273
|
|
|
207
274
|
- Prefer files inside your app's document, cache, or temporary directory.
|
|
208
|
-
- `file://` URIs and plain local paths
|
|
275
|
+
- `file://` URIs and plain local paths both work. The package strips the `file://` scheme and percent-decoding for you, so a path containing spaces or non-ASCII characters is handled correctly; passing the raw URI straight to FFmpeg would write to a file literally named `my%20clip.mp4`.
|
|
209
276
|
- On Android, copy a `content://` document into application storage before processing when the native library cannot open it directly.
|
|
210
277
|
- Copy photo-library or document-picker assets when the provider gives temporary or security-scoped access.
|
|
211
278
|
- Ensure the output directory already exists.
|
|
@@ -343,6 +410,44 @@ Returns the version reported by the bundled native FFmpeg library.
|
|
|
343
410
|
function getFFmpegVersion(): string
|
|
344
411
|
```
|
|
345
412
|
|
|
413
|
+
### `listEncoders()`
|
|
414
|
+
|
|
415
|
+
Returns the encoder names the bundled FFmpeg build can write. The result is cached after the first call.
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
function listEncoders(): Promise<string[]>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### `listDecoders()`
|
|
422
|
+
|
|
423
|
+
Returns the decoder names the bundled FFmpeg build can read.
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
function listDecoders(): Promise<string[]>
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### `pickEncoder(candidates)`
|
|
430
|
+
|
|
431
|
+
Returns the first name in `candidates` that the build provides, or `undefined` when none are available. Use it to write one command that runs on both platforms.
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
function pickEncoder(candidates: string[]): Promise<string | undefined>
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
const hevc = await pickEncoder(['libx265', 'hevc_videotoolbox'])
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### `normalizePath(value)`
|
|
442
|
+
|
|
443
|
+
Converts a `file://` URI into the plain path FFmpeg expects, and returns anything else untouched.
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
function normalizePath(value: string): string
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
`execute()`, `probe()`, and `getMediaInformation()` already apply this to every argument, so you rarely need to call it directly. It is exported for cases where you build a path yourself โ a concat list file, for example, whose entries FFmpeg reads verbatim.
|
|
450
|
+
|
|
346
451
|
### `FFmpegSessionResult`
|
|
347
452
|
|
|
348
453
|
```typescript
|
|
@@ -358,6 +463,8 @@ type FFmpegSessionResult = {
|
|
|
358
463
|
}
|
|
359
464
|
```
|
|
360
465
|
|
|
466
|
+
`state` is one of `created`, `running`, `failed`, or `completed`, and reports the same values on both platforms.
|
|
467
|
+
|
|
361
468
|
Always check `success` or `cancelled`; Promise resolution means the native session completed, not necessarily that FFmpeg returned a success code.
|
|
362
469
|
|
|
363
470
|
## ๐ Usage examples
|
|
@@ -391,6 +498,34 @@ if (!result.success) {
|
|
|
391
498
|
}
|
|
392
499
|
```
|
|
393
500
|
|
|
501
|
+
### Transcode to H.264 on both platforms
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
import { execute, pickEncoder } from 'munim-ffmpeg'
|
|
505
|
+
|
|
506
|
+
const encoder = await pickEncoder(['libx264', 'h264_videotoolbox'])
|
|
507
|
+
if (!encoder) throw new Error('No H.264 encoder available in this build')
|
|
508
|
+
|
|
509
|
+
// -preset is an x264 option; VideoToolbox rejects it.
|
|
510
|
+
const quality = encoder === 'libx264' ? ['-preset', 'veryfast', '-crf', '23'] : ['-b:v', '2M']
|
|
511
|
+
|
|
512
|
+
const result = await execute([
|
|
513
|
+
'-y',
|
|
514
|
+
'-i',
|
|
515
|
+
inputPath,
|
|
516
|
+
'-c:v',
|
|
517
|
+
encoder,
|
|
518
|
+
...quality,
|
|
519
|
+
'-c:a',
|
|
520
|
+
'aac',
|
|
521
|
+
'-pix_fmt',
|
|
522
|
+
'yuv420p',
|
|
523
|
+
outputPath,
|
|
524
|
+
])
|
|
525
|
+
|
|
526
|
+
if (!result.success) throw new Error(result.failStackTrace ?? result.output)
|
|
527
|
+
```
|
|
528
|
+
|
|
394
529
|
### Generate a thumbnail
|
|
395
530
|
|
|
396
531
|
```typescript
|
|
@@ -456,25 +591,17 @@ if (result.success) {
|
|
|
456
591
|
}
|
|
457
592
|
```
|
|
458
593
|
|
|
459
|
-
##
|
|
460
|
-
|
|
461
|
-
The JavaScript, TypeScript, Swift, Kotlin, and generated Nitro bridge code in this repository are Apache-2.0 licensed. Native FFmpeg binaries are supplied by separate compatibility packages:
|
|
594
|
+
## Licensing
|
|
462
595
|
|
|
463
|
-
|
|
464
|
-
| -------- | --------------------------------------------------- | ------- |
|
|
465
|
-
| iOS | `ffmpeg-kit-ios-https-alt` | 6.0 |
|
|
466
|
-
| Android | `io.github.jamaismagic.ffmpeg:ffmpeg-kit-main-16kb` | 6.1.4 |
|
|
596
|
+
The JavaScript, TypeScript, Swift, Kotlin, C core, and generated Nitro bridge in this repository are Apache-2.0.
|
|
467
597
|
|
|
468
|
-
The bundled
|
|
598
|
+
The bundled FFmpeg 9.0.1 is **LGPLv3**, on both platforms. It is configured without `--enable-gpl`, so no x264, x265, xvid, or vid.stab: H.264 and HEVC encoding come from VideoToolbox and MediaCodec instead. The external libraries it does link are LAME (LGPL), Opus (BSD), libvpx (BSD), dav1d (BSD), and mbedTLS (Apache-2.0) on Android.
|
|
469
599
|
|
|
470
|
-
|
|
600
|
+
In practice that means your application does **not** inherit GPL obligations. LGPL still applies: the FFmpeg libraries are linked and their license and notices must be conveyed with your app, and users must be able to relink against a modified FFmpeg. The exact configuration used is recorded in [`scripts/ffmpeg/build-ios.sh`](./scripts/ffmpeg/build-ios.sh) and [`build-android.sh`](./scripts/ffmpeg/build-android.sh), and the binaries can be reproduced from them.
|
|
471
601
|
|
|
472
|
-
|
|
473
|
-
2. Identify the codecs and linked libraries used by your product.
|
|
474
|
-
3. Follow the applicable LGPL, GPL, attribution, relinking, and source-offer requirements.
|
|
475
|
-
4. Treat the current Android build as GPL-enabled, and reassess licensing again if you replace either native dependency.
|
|
602
|
+
If you need x264 or x265, add `--enable-gpl --enable-libx264 --enable-libx265` to those scripts and rebuild โ but then your application does inherit GPLv3.
|
|
476
603
|
|
|
477
|
-
See [FFmpeg legal guidance](https://ffmpeg.org/legal.html). This section is an engineering
|
|
604
|
+
See [FFmpeg legal guidance](https://ffmpeg.org/legal.html). This section is an engineering summary, not legal advice.
|
|
478
605
|
|
|
479
606
|
## ๐ Troubleshooting
|
|
480
607
|
|
|
@@ -496,7 +623,7 @@ Rebuild the native app after installing both `munim-ffmpeg` and `react-native-ni
|
|
|
496
623
|
|
|
497
624
|
### A codec or filter is missing
|
|
498
625
|
|
|
499
|
-
Native FFmpeg variants do not bundle every codec, filter, or third-party library.
|
|
626
|
+
Native FFmpeg variants do not bundle every codec, filter, or third-party library, and the iOS and Android builds are not identical. Call `listEncoders()` or `listDecoders()` to see what the running build actually has, and prefer `pickEncoder()` over a hard-coded name. `libx264` in particular exists only on Android โ see [Bundled FFmpeg builds](#bundled-ffmpeg-builds).
|
|
500
627
|
|
|
501
628
|
### The Promise resolved but the command failed
|
|
502
629
|
|
|
@@ -504,24 +631,30 @@ Inspect `result.success`, `result.cancelled`, `result.returnCode`, `result.outpu
|
|
|
504
631
|
|
|
505
632
|
### iOS pod or build errors
|
|
506
633
|
|
|
507
|
-
Run `pod install` after installation and rebuild from a clean native development build.
|
|
634
|
+
Run `pod install` after installation and rebuild from a clean native development build. If the linker cannot find `MunimFFmpeg.xcframework`, the native binaries were not downloaded โ run `npx munim-ffmpeg-fetch-binaries`.
|
|
508
635
|
|
|
509
636
|
### Android build errors
|
|
510
637
|
|
|
511
638
|
Use Android API 24 or newer, JDK 17, and the React Native New Architecture. Clear stale Gradle build output after changing native dependency versions.
|
|
512
639
|
|
|
640
|
+
If the build fails on duplicate `libc++_shared.so`, make sure the config plugin ran (Expo) or add `android.packagingOptions.pickFirsts=**/libc++_shared.so` to `gradle.properties` (bare React Native).
|
|
641
|
+
|
|
642
|
+
If `System.loadLibrary` cannot find `munimffmpeg9`, the native binaries were not downloaded โ run `npx munim-ffmpeg-fetch-binaries`.
|
|
643
|
+
|
|
513
644
|
## Development
|
|
514
645
|
|
|
515
646
|
```bash
|
|
516
647
|
npm install
|
|
517
|
-
npm run codegen
|
|
518
|
-
npm run typecheck
|
|
519
|
-
npm run typecheck:example
|
|
520
|
-
npm run build
|
|
521
|
-
npm run pack:dry-run
|
|
648
|
+
npm run check # codegen, typecheck, example typecheck, build, pack dry-run
|
|
522
649
|
```
|
|
523
650
|
|
|
524
|
-
|
|
651
|
+
Individual steps are available as `npm run codegen`, `typecheck`, `typecheck:example`, and `build`.
|
|
652
|
+
|
|
653
|
+
Nitrogen output under `nitrogen/generated` is committed. Change the `.nitro.ts` specification and rerun `npm run codegen` instead of editing generated files directly.
|
|
654
|
+
|
|
655
|
+
### Example app
|
|
656
|
+
|
|
657
|
+
`example/` is an Expo app that runs a 24-check device suite: H.264 and HEVC encoding, VP9/Opus in WebM, MP3, AAC, scaling and multi-step filter graphs, muxing, demuxing, trimming, concatenation, thumbnails, audio resampling, awkward file paths, concurrent sessions, single and global cancellation, protocol support, and both failure paths. Fixtures are generated in JavaScript, so the suite needs no network or bundled media. Results are rendered on screen, written to `munim-ffmpeg-suite.json` in the app's document directory, and logged as `MUNIM_FFMPEG_SUITE_RESULT`.
|
|
525
658
|
|
|
526
659
|
```bash
|
|
527
660
|
npm run example:ios
|
|
@@ -529,15 +662,27 @@ npm run example:ios
|
|
|
529
662
|
npm run example:android
|
|
530
663
|
```
|
|
531
664
|
|
|
532
|
-
|
|
665
|
+
FFmpeg encoding is slow in a simulator or emulator; run the suite on a physical device.
|
|
666
|
+
|
|
667
|
+
### Rebuilding FFmpeg
|
|
668
|
+
|
|
669
|
+
```bash
|
|
670
|
+
npm run binaries:build # every architecture, ~40 minutes
|
|
671
|
+
npm run binaries:package # bundles them and records the checksum
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
See [`scripts/ffmpeg/README.md`](./scripts/ffmpeg/README.md) for what the build does and the platform quirks it works around.
|
|
675
|
+
|
|
676
|
+
### Releasing
|
|
677
|
+
|
|
678
|
+
Releases run locally from a clean `main`; this repository does not use GitHub Actions.
|
|
533
679
|
|
|
534
680
|
```bash
|
|
535
681
|
npm run check
|
|
536
|
-
|
|
537
|
-
npm publish --access public
|
|
682
|
+
npm run release:local
|
|
538
683
|
```
|
|
539
684
|
|
|
540
|
-
|
|
685
|
+
`release:local` runs semantic-release with the npm token from the macOS Keychain and the GitHub CLI token, so commit messages must follow Conventional Commits. It also uploads `dist-binaries/munim-ffmpeg-binaries.tar.gz` to the GitHub release, which is where `postinstall` fetches it from โ so run `npm run binaries:package` first.
|
|
541
686
|
|
|
542
687
|
## ๐ Contributing
|
|
543
688
|
|
package/android/build.gradle
CHANGED
|
@@ -139,6 +139,6 @@ dependencies {
|
|
|
139
139
|
// Add a dependency on NitroModules
|
|
140
140
|
implementation project(":react-native-nitro-modules")
|
|
141
141
|
|
|
142
|
-
//
|
|
143
|
-
|
|
142
|
+
// FFmpeg 9 and the in-process fftools core ship as prebuilt .so files in
|
|
143
|
+
// src/main/jniLibs; see scripts/ffmpeg/ for the build.
|
|
144
144
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
package com.margelo.nitro.munimffmpeg
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.Keep
|
|
4
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Thin wrapper over FFmpeg 9's own command-line tools, compiled to run inside
|
|
8
|
+
* the app process.
|
|
9
|
+
*
|
|
10
|
+
* fftools keeps its parsed command in file-scope globals, so exactly one
|
|
11
|
+
* execution runs at a time; the native core serialises callers and cancels
|
|
12
|
+
* anything still queued when [nativeCancel] is called.
|
|
13
|
+
*/
|
|
14
|
+
@Keep
|
|
15
|
+
@DoNotStrip
|
|
16
|
+
object FFmpegNative {
|
|
17
|
+
init {
|
|
18
|
+
System.loadLibrary("munimffmpeg9")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Return code the tools report when a run was cancelled. */
|
|
22
|
+
const val CANCELLED = 255
|
|
23
|
+
|
|
24
|
+
external fun nativeVersion(): String
|
|
25
|
+
|
|
26
|
+
external fun nativeExecute(arguments: Array<String>, stdoutPath: String): Int
|
|
27
|
+
|
|
28
|
+
external fun nativeExecuteProbe(arguments: Array<String>, outputPath: String): Int
|
|
29
|
+
|
|
30
|
+
external fun nativeCancel()
|
|
31
|
+
|
|
32
|
+
data class Statistics(
|
|
33
|
+
val timeMs: Double,
|
|
34
|
+
val sizeBytes: Double,
|
|
35
|
+
val bitrateKbits: Double,
|
|
36
|
+
val speed: Double,
|
|
37
|
+
val videoFrameNumber: Double,
|
|
38
|
+
val fps: Double,
|
|
39
|
+
val quality: Double,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
@Volatile
|
|
43
|
+
private var logSink: ((String) -> Unit)? = null
|
|
44
|
+
|
|
45
|
+
@Volatile
|
|
46
|
+
private var statisticsSink: ((Statistics) -> Unit)? = null
|
|
47
|
+
|
|
48
|
+
fun <T> withCallbacks(
|
|
49
|
+
onLog: ((String) -> Unit)?,
|
|
50
|
+
onStatistics: ((Statistics) -> Unit)?,
|
|
51
|
+
body: () -> T,
|
|
52
|
+
): T {
|
|
53
|
+
logSink = onLog
|
|
54
|
+
statisticsSink = onStatistics
|
|
55
|
+
try {
|
|
56
|
+
return body()
|
|
57
|
+
} finally {
|
|
58
|
+
logSink = null
|
|
59
|
+
statisticsSink = null
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@JvmStatic
|
|
64
|
+
@Keep
|
|
65
|
+
@DoNotStrip
|
|
66
|
+
fun onLog(message: String) {
|
|
67
|
+
logSink?.invoke(message)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@JvmStatic
|
|
71
|
+
@Keep
|
|
72
|
+
@DoNotStrip
|
|
73
|
+
fun onStatistics(
|
|
74
|
+
timeMs: Double,
|
|
75
|
+
sizeBytes: Double,
|
|
76
|
+
bitrateKbits: Double,
|
|
77
|
+
speed: Double,
|
|
78
|
+
videoFrameNumber: Double,
|
|
79
|
+
fps: Double,
|
|
80
|
+
quality: Double,
|
|
81
|
+
) {
|
|
82
|
+
statisticsSink?.invoke(
|
|
83
|
+
Statistics(timeMs, sizeBytes, bitrateKbits, speed, videoFrameNumber, fps, quality)
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
}
|