group 'io.swipelab.ux' version '1.0-SNAPSHOT' buildscript { ext.kotlin_version = '1.9.22' repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:8.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } rootProject.allprojects { repositories { google() mavenCentral() } } apply plugin: 'com.android.library' apply plugin: 'kotlin-android' // FFmpeg software H.264 decoder — OPT-IN, disabled by default. // // The decoder (a fallback for devices whose hardware decoder fails) carries // LGPL + H.264 patent obligations, so it is only bundled when the consuming // app asks for it, via a flag in its pubspec.yaml: // // ux: // enable_ffmpeg: true // // (or -Pux.enable_ffmpeg=true on the Gradle command line as a CI override). // When off: buildFfmpegJni is skipped, no libffmpegJNI.so is packaged, // FfmpegLibrary.isAvailable() returns false, the renderer is never added, and // playback uses the platform MediaCodec decoders only. See android/ffmpeg/README.md. // // This pubspec flag is the only switch: ux's build_runner builder also reads // `ux: enable_ffmpeg` and generates a `kUxEnableFfmpeg` constant the app uses // to register the LGPL license notice — no --dart-define needed. def ffmpegEnabled = { // CI / command-line override takes precedence over the pubspec flag. def prop = project.findProperty('ux.enable_ffmpeg') if (prop != null) return prop.toString().trim().equalsIgnoreCase('true') // rootProject is the consuming app's android/ project; its pubspec.yaml // sits one directory up. Match a top-level `ux:` block (not the indented // `ux:` dependency entry) and its nested `enable_ffmpeg:` value. def pubspec = new File(rootProject.projectDir.parentFile, 'pubspec.yaml') if (!pubspec.exists()) return false boolean inUxBlock = false boolean enabled = false pubspec.eachLine { raw -> def hash = raw.indexOf('#') def line = hash >= 0 ? raw.substring(0, hash) : raw if (line.trim().isEmpty()) return if (!Character.isWhitespace(line.charAt(0))) { // A new top-level key: we're in the config block only if it's `ux:`. inUxBlock = line.trim().startsWith('ux:') } else if (inUxBlock) { def t = line.trim() if (t.startsWith('enable_ffmpeg:')) { enabled = t.substring('enable_ffmpeg:'.length()).trim().equalsIgnoreCase('true') } } } return enabled }() android { namespace 'io.swipelab.ux' // media3 1.9.2 requires compileSdk >= 35; match the app/Flutter default (36). compileSdk 36 // Match Flutter's pinned NDK (flutter_tools gradle_utils.dart) and the // app; AGP 8.1's default NDK (25.x) isn't installed on dev machines. ndkVersion '28.2.13676358' defaultConfig { minSdk 21 externalNativeBuild { cmake { cppFlags "" } } } externalNativeBuild { cmake { path "src/main/jni/CMakeLists.txt" } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = '1.8' } sourceSets { main { // libffmpegJNI.so is built by the buildFfmpegJni task into // build/jniLibs//. Only reference it when FFmpeg is enabled, // so a stale .so from a previous enabled build is not packaged // into a now-disabled build. Lets AGP package the .so into the // AAR without committing native binaries to the repo. if (ffmpegEnabled) { jniLibs.srcDirs += "$buildDir/jniLibs" } } } } // FFmpeg video decoder build (opt-in — see the `ffmpegEnabled` flag near the // top of this file and android/ffmpeg/README.md). When enabled, the first // build for a checkout clones Media3 + FFmpeg into build/ffmpeg-work/ and // produces libffmpegJNI.so per ABI (~30 min for the FFmpeg static-lib step the // first time, fast after); Gradle UP-TO-DATE checking skips it when the // vendored JNI source + CMakeLists are unchanged. def ffmpegSrcDir = file("$projectDir/ffmpeg") def ffmpegWorkDir = file("$buildDir/ffmpeg-work") def ffmpegOutDir = file("$buildDir/jniLibs") def ndkCmakeBin = "${android.sdkDirectory}/cmake/3.22.1/bin" def supportedAbis = ['armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'] task buildFfmpegJni(type: Exec) { group = 'build' description = 'Clones Media3 + FFmpeg if needed, builds libffmpegJNI.so per Android ABI' inputs.file "$ffmpegSrcDir/ffmpeg_jni.cc" inputs.file "$ffmpegSrcDir/CMakeLists.txt" inputs.file "$ffmpegSrcDir/build_ffmpeg.sh" supportedAbis.each { abi -> outputs.file "$ffmpegOutDir/$abi/libffmpegJNI.so" } workingDir ffmpegSrcDir commandLine 'bash', "$ffmpegSrcDir/build_ffmpeg.sh" environment 'JNI_SRC', ffmpegSrcDir.absolutePath environment 'NDK_PATH', android.ndkDirectory.absolutePath environment 'CMAKE_PATH', ndkCmakeBin environment 'OUTPUT_DIR', ffmpegOutDir.absolutePath environment 'WORK_DIR', ffmpegWorkDir.absolutePath } afterEvaluate { if (ffmpegEnabled) { preBuild.dependsOn buildFfmpegJni logger.lifecycle("ux: FFmpeg software H.264 decoder ENABLED (ux: enable_ffmpeg: true).") } else { logger.lifecycle("ux: FFmpeg software H.264 decoder disabled (default). " + "Add 'ux:\\n enable_ffmpeg: true' to the app pubspec.yaml to bundle it.") } } dependencies { // CameraX for scanner preview + frame analysis + ux.camera. // 1.4.0+ ships 16-KB-aligned `libimage_processing_util_jni.so` // (Android 15 requirement); 1.3.x failed the elf-alignment check. def cameraxVersion = '1.4.2' implementation "androidx.camera:camera-core:$cameraxVersion" implementation "androidx.camera:camera-camera2:$cameraxVersion" implementation "androidx.camera:camera-lifecycle:$cameraxVersion" implementation "androidx.camera:camera-view:$cameraxVersion" implementation "androidx.camera:camera-video:$cameraxVersion" // ProcessLifecycleOwner so CameraInstance can release the camera // when the host app backgrounds (and re-acquire on foreground). // camera-lifecycle pulls in lifecycle-common but not the process // observer; this adds it. implementation 'androidx.lifecycle:lifecycle-process:2.7.0' // Pure-Kotlin/Java QR decoder. ~470 KB jar, no Play Services dep. implementation 'com.google.zxing:core:3.5.3' // Media3 for ux.video_player. Same version line video_player_android // 2.9.5 pulls in (1.9.x) so the spike fork can coexist during the // Phase 2/3 migration without dragging in two ExoPlayer copies. def media3Version = '1.9.2' implementation "androidx.media3:media3-exoplayer:$media3Version" implementation "androidx.media3:media3-common:$media3Version" }