Publish Documentation / Build & publish docs to gitea-pages (push) Canceled after 0s
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 15m44s
🏗️ Build and Test JellyTau / Supply Chain (push) Successful in 34s
Traceability Validation / Check Requirement Traces (push) Successful in 24s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 4m33s
Turning a subtitle on did nothing even after DR-259 made them load. ExoPlayer decodes subtitles and delivers them to a listener; it draws none itself. A PlayerView would supply the view that does, but native video here is a bare TextureView the WebView composites over — so nothing held the cues and every one was decoded, delivered and dropped. There was no onCues, no TextOutput and no SubtitleView anywhere in the app, and media3-ui was not even a dependency. The gap was invisible for as long as every subtitle URL 404ed: with no text track to select there was never a cue to lose, so fixing the URL is what exposed it. media3-ui's SubtitleView now takes each CueGroup and is attached at index 1 of the content view — above the video, still below the WebView, so cues sit over the picture and under the app's own controls. It is fitted to the letterboxed video rect rather than the screen, so cues stay inside the picture and follow it on rotation, and is removed by the same teardown that detaches the surface (the defect DR-184 exists to prevent). Verified on a device: track selected with no "Invalid subtitle track index", SubtitleView attached at the fitted rect per the live view hierarchy, and cues legible on screen during playback. TRACES: UR-020, UR-003 | DR-260
160 lines
7.1 KiB
Kotlin
160 lines
7.1 KiB
Kotlin
import java.util.Properties
|
|
|
|
plugins {
|
|
id("com.android.application")
|
|
id("org.jetbrains.kotlin.android")
|
|
id("rust")
|
|
}
|
|
|
|
val tauriProperties = Properties().apply {
|
|
val propFile = file("tauri.properties")
|
|
if (propFile.exists()) {
|
|
propFile.inputStream().use { load(it) }
|
|
}
|
|
}
|
|
|
|
// Release signing: loaded from gen/android/keystore.properties if present.
|
|
// Falls back to no signing config (debug-signed) when the file is absent.
|
|
val keystoreProperties = Properties().apply {
|
|
val propFile = rootProject.file("keystore.properties")
|
|
if (propFile.exists()) {
|
|
propFile.inputStream().use { load(it) }
|
|
}
|
|
}
|
|
|
|
// Side-by-side release: set by `scripts/build-android.sh release --debug`, which
|
|
// exports JT_SIDE_BY_SIDE=1. It puts a fully R8-minified release build into the
|
|
// debug applicationId slot, signed with the local debug keystore — so you can
|
|
// test what minification actually produces (R8 stripping JNI-loaded classes has
|
|
// broken release APKs here before) without the real signing key and without
|
|
// uninstalling your working install. Unset in CI, so distributable release
|
|
// builds are untouched.
|
|
val sideBySideRelease = System.getenv("JT_SIDE_BY_SIDE").let { it == "1" || it == "true" }
|
|
|
|
android {
|
|
compileSdk = 36
|
|
namespace = "com.dtourolle.jellytau"
|
|
defaultConfig {
|
|
manifestPlaceholders["usesCleartextTraffic"] = "false"
|
|
// Launcher/app names come from placeholders so the debug build can
|
|
// rename itself without touching the generated strings.xml (a
|
|
// resValue() override there would collide with Tauri's own entries).
|
|
manifestPlaceholders["appLabel"] = "@string/app_name"
|
|
manifestPlaceholders["activityLabel"] = "@string/main_activity_title"
|
|
applicationId = "com.dtourolle.jellytau"
|
|
minSdk = 24
|
|
targetSdk = 36
|
|
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
|
|
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
|
|
// Required to run the on-device conformance suite
|
|
// (src/androidTest). See docs/specs/media-player-controller.md.
|
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
}
|
|
signingConfigs {
|
|
create("release") {
|
|
keystoreProperties.getProperty("storeFile")?.let {
|
|
storeFile = file(it)
|
|
storePassword = keystoreProperties.getProperty("storePassword")
|
|
keyAlias = keystoreProperties.getProperty("keyAlias")
|
|
keyPassword = keystoreProperties.getProperty("keyPassword")
|
|
}
|
|
}
|
|
}
|
|
buildTypes {
|
|
getByName("debug") {
|
|
// Distinct applicationId so the debug build installs SIDE BY SIDE
|
|
// with a release/store install instead of demanding an uninstall
|
|
// (different signing keys on the same package = INSTALL_FAILED_
|
|
// UPDATE_INCOMPATIBLE). It gets its own data dir, its own settings
|
|
// and its own offline cache — the two are fully independent apps.
|
|
//
|
|
// This changes only the *application* id. The Kotlin/JNI classes
|
|
// stay in the `namespace` package (com.dtourolle.jellytau), so the
|
|
// fully-qualified class names Rust looks up over JNI, the manifest
|
|
// <service> entry and the R8 keep rules are all unaffected. The
|
|
// FileProvider authority is already ${applicationId}-relative.
|
|
applicationIdSuffix = ".debug"
|
|
versionNameSuffix = "-debug"
|
|
manifestPlaceholders["appLabel"] = "JellyTau Debug"
|
|
manifestPlaceholders["activityLabel"] = "JellyTau Debug"
|
|
manifestPlaceholders["usesCleartextTraffic"] = "true"
|
|
isDebuggable = true
|
|
isJniDebuggable = true
|
|
isMinifyEnabled = false
|
|
packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
|
|
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
|
|
jniLibs.keepDebugSymbols.add("*/x86/*.so")
|
|
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
|
|
}
|
|
}
|
|
getByName("release") {
|
|
if (sideBySideRelease) {
|
|
// Same slot, name and version scheme as the debug build type,
|
|
// plus "-release" so you can tell from Settings > Apps which of
|
|
// the two is currently sitting there. Signed with the debug
|
|
// keystore: it shares a signature with the debug build, so the
|
|
// two replace each other cleanly instead of colliding.
|
|
applicationIdSuffix = ".debug"
|
|
versionNameSuffix = "-debug-release"
|
|
manifestPlaceholders["appLabel"] = "JellyTau Debug"
|
|
manifestPlaceholders["activityLabel"] = "JellyTau Debug"
|
|
signingConfig = signingConfigs.getByName("debug")
|
|
} else if (keystoreProperties.getProperty("storeFile") != null) {
|
|
signingConfig = signingConfigs.getByName("release")
|
|
}
|
|
isMinifyEnabled = true
|
|
proguardFiles(
|
|
*fileTree(".") { include("**/*.pro") }
|
|
.plus(getDefaultProguardFile("proguard-android-optimize.txt"))
|
|
.toList().toTypedArray()
|
|
)
|
|
}
|
|
}
|
|
// Java 17 bytecode. AGP 8.11 already requires a JDK 17 toolchain to run
|
|
// (the builder image ships openjdk-17), so "1.8" was only capping the
|
|
// bytecode we emit, not the JDK in use. Kotlin's jvmTarget and javac's
|
|
// source/targetCompatibility must agree or AGP 8 fails the build, so all
|
|
// three move together.
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
kotlinOptions {
|
|
jvmTarget = "17"
|
|
}
|
|
buildFeatures {
|
|
buildConfig = true
|
|
}
|
|
}
|
|
|
|
rust {
|
|
rootDirRel = "../../../"
|
|
}
|
|
|
|
dependencies {
|
|
implementation("androidx.webkit:webkit:1.14.0")
|
|
implementation("androidx.appcompat:appcompat:1.7.1")
|
|
implementation("androidx.activity:activity-ktx:1.10.1")
|
|
implementation("com.google.android.material:material:1.12.0")
|
|
|
|
// Media3 dependencies for audio playback
|
|
implementation("androidx.media3:media3-exoplayer:1.5.0")
|
|
implementation("androidx.media3:media3-exoplayer-hls:1.5.0")
|
|
implementation("androidx.media3:media3-session:1.5.0")
|
|
implementation("androidx.media3:media3-common:1.5.0")
|
|
// SubtitleView. ExoPlayer delivers cues to a listener and draws none of them
|
|
// itself: without a view to hand them to, a selected subtitle track renders
|
|
// nowhere. See JellyTauPlayer.onCues. (DR-260)
|
|
implementation("androidx.media3:media3-ui:1.5.0")
|
|
implementation("com.google.guava:guava:33.0.0-android")
|
|
|
|
// Media library for VolumeProviderCompat (remote volume control)
|
|
implementation("androidx.media:media:1.7.0")
|
|
|
|
testImplementation("junit:junit:4.13.2")
|
|
androidTestImplementation("androidx.test.ext:junit:1.1.4")
|
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
|
|
androidTestImplementation("androidx.test:runner:1.5.2")
|
|
}
|
|
|
|
apply(from = "tauri.build.gradle.kts") |