Initial commit
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
// Media3 Engine plugin — Android-only ExoPlayer + FFmpeg audio decoder bridge.
|
||||
//
|
||||
// 依赖:
|
||||
// - Maven Central: media3-exoplayer / media3-datasource / media3-decoder 1.10.1
|
||||
// - 本地 AAR : media3-decoder-ffmpeg-1.10.1-arm64.aar
|
||||
// 由 fileTree 从主 app 的 android/app/libs/ 加载。AAR 由
|
||||
// scripts/build_media3_ffmpeg.sh 自编译产出,CHECKSUMS.txt
|
||||
// 记录 SHA-256。
|
||||
//
|
||||
// AAR 缺失时 preBuild 任务会 fail-fast 抛出带恢复指引的错误(详见 README)。
|
||||
|
||||
import java.security.MessageDigest
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
group = "com.smplayer.media3_engine"
|
||||
version = "0.0.1"
|
||||
|
||||
android {
|
||||
namespace = "com.smplayer.media3_engine"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
ndk {
|
||||
// 与主 app 保持一致:仅 arm64-v8a。其它 ABI 在 FFmpeg AAR 中也不存在,
|
||||
// 即便 ABI filter 漏了也只是无声的浪费(不会 crash)。
|
||||
abiFilters += listOf("arm64-v8a")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = false
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
// 自编译 FFmpeg audio decoder extension(arm64-v8a only,LGPL 配置)。AAR 由
|
||||
// scripts/build_media3_ffmpeg.sh 产出,放在 android/app/libs/。
|
||||
// 这里只声明 compileOnly:AGP 8 起禁止 library module 直接 implementation 本地
|
||||
// .aar("Direct local .aar file dependencies are not supported when building an
|
||||
// AAR"),实际把 AAR 打入最终 APK 的责任落到主 app/build.gradle.kts 的
|
||||
// `implementation(fileTree("libs"))`。本 plugin 编译期只需要 FfmpegAudioRenderer
|
||||
// 等符号的可见性。
|
||||
val media3Version = "1.10.1"
|
||||
val ffmpegAarDir = file("../../../android/app/libs")
|
||||
val ffmpegAarName = "media3-decoder-ffmpeg-$media3Version-arm64.aar"
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.media3:media3-exoplayer:$media3Version")
|
||||
implementation("androidx.media3:media3-datasource:$media3Version")
|
||||
implementation("androidx.media3:media3-decoder:$media3Version")
|
||||
implementation("androidx.media3:media3-ui:$media3Version")
|
||||
compileOnly(fileTree(mapOf("dir" to ffmpegAarDir, "include" to listOf(ffmpegAarName))))
|
||||
}
|
||||
|
||||
// AAR 缺失 / 篡改 fail-fast:在 preBuild 之前校验文件存在性 + SHA-256 与
|
||||
// `android/app/libs/CHECKSUMS.txt` 一致。CI/Clean checkout 友好。
|
||||
val verifyMedia3FfmpegAar by tasks.registering {
|
||||
group = "verification"
|
||||
description = "Verify media3 FFmpeg decoder AAR exists and matches CHECKSUMS.txt."
|
||||
val checksumsFile = ffmpegAarDir.resolve("CHECKSUMS.txt")
|
||||
val aarFile = ffmpegAarDir.resolve(ffmpegAarName)
|
||||
inputs.file(aarFile).withPropertyName("aar").withPathSensitivity(org.gradle.api.tasks.PathSensitivity.NONE)
|
||||
inputs.file(checksumsFile).withPropertyName("checksums").withPathSensitivity(org.gradle.api.tasks.PathSensitivity.NONE)
|
||||
doLast {
|
||||
if (!aarFile.isFile) {
|
||||
throw GradleException(
|
||||
"""
|
||||
|Media3 FFmpeg decoder AAR is missing:
|
||||
| expected: ${aarFile.absolutePath}
|
||||
|
|
||||
|This AAR is self-built (LGPL config, arm64-v8a,
|
||||
|codecs: dca/ac3/eac3/truehd/opus/vorbis/flac).
|
||||
|To produce it, run from the repo root:
|
||||
|
|
||||
| scripts/build_media3_ffmpeg.sh
|
||||
|
|
||||
|Prerequisites: NDK r26b, JDK 17, ~30-60min wall-clock. See
|
||||
|plugins/media3_engine/README.md for full instructions and Docker recipe.
|
||||
|
|
||||
|If you only build the desktop (Windows/macOS) target, this Android
|
||||
|module is not on the dependency path and you can ignore this error.
|
||||
""".trimMargin()
|
||||
)
|
||||
}
|
||||
if (!checksumsFile.isFile) {
|
||||
throw GradleException(
|
||||
"Media3 AAR checksum manifest is missing: ${checksumsFile.absolutePath}\n" +
|
||||
"Restore CHECKSUMS.txt from git or rerun scripts/build_media3_ffmpeg.sh."
|
||||
)
|
||||
}
|
||||
// CHECKSUMS.txt 行格式:`<sha256> <filename>`,可有 # 开头注释 / 空行
|
||||
val expectedSha = checksumsFile.readLines()
|
||||
.map { it.trim() }
|
||||
.firstOrNull { it.isNotEmpty() && !it.startsWith("#") && it.endsWith(ffmpegAarName) }
|
||||
?.substringBefore(' ')
|
||||
?.trim()
|
||||
?.lowercase()
|
||||
if (expectedSha.isNullOrBlank() || !expectedSha.matches(Regex("^[0-9a-f]{64}$"))) {
|
||||
throw GradleException(
|
||||
"Media3 AAR checksum entry missing or malformed for $ffmpegAarName in " +
|
||||
"${checksumsFile.absolutePath}. Expected a line: '<sha256> $ffmpegAarName'."
|
||||
)
|
||||
}
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
aarFile.inputStream().use { input ->
|
||||
val buffer = ByteArray(8192)
|
||||
while (true) {
|
||||
val n = input.read(buffer)
|
||||
if (n <= 0) break
|
||||
md.update(buffer, 0, n)
|
||||
}
|
||||
}
|
||||
val actualSha = md.digest().joinToString("") { "%02x".format(it) }
|
||||
if (actualSha != expectedSha) {
|
||||
throw GradleException(
|
||||
"""
|
||||
|Media3 FFmpeg decoder AAR checksum mismatch — refusing to build.
|
||||
| file : ${aarFile.absolutePath}
|
||||
| expected : $expectedSha (from CHECKSUMS.txt)
|
||||
| actual : $actualSha
|
||||
|
|
||||
|The AAR has been replaced/corrupted since CHECKSUMS.txt was written.
|
||||
|If you intentionally rebuilt it, scripts/build_media3_ffmpeg.sh will
|
||||
|refresh CHECKSUMS.txt automatically; otherwise restore the file from git.
|
||||
""".trimMargin()
|
||||
)
|
||||
}
|
||||
logger.lifecycle("[verifyMedia3FfmpegAar] OK — sha256 $actualSha")
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
tasks.named("preBuild").configure {
|
||||
dependsOn(verifyMedia3FfmpegAar)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user