Skip to content

Commit 5997a10

Browse files
committed
Release v0.1.56: split mobile/TV builds, fix crashes, preheat sources
Split the app into `mobile` and `tv` product flavors on one codebase rather than forking it: every defect found in the last round of device reports lived in shared logic and would have had to be fixed twice. TV drops Google Cast entirely (a TV is where a cast ends up, and Fire TV's Play Services cannot serve the Cast module at all), which removes 163 GMS + 107 mediarouter + 14 media3-cast classes and ~1.1 MB. applicationId is unchanged across flavors so every existing install updates in place. Crashes, each traced to a specific device report: - Declare PlatformScheduler$PlatformSchedulerService. Media3's download scheduler targets this JobService and an undeclared component is rejected by JobScheduler, killing the app on the main thread whenever the download manager reconsidered its queue. - Serialise MP4 exports behind a process-wide slot and defer them while playback is running. The export observer enqueues one worker per episode, so finishing a batch download ran several Media3 Transformers at once, each buffering encoded samples in an unbounded queue; six OOMs traced there, one session recording 31 CRASH and 26 LOW_MEMORY exits. - Dedupe the series chain. Two cached ids can resolve to one entry, and the detail rows key by media id: "Key ... was already used" took the app down. - Turn off text-selection action mode on the TV editor. Its async smart-select task dereferenced a null Layout inside android.widget after a re-layout. - Add largeHeap, prune the WorkManager DB, and keep secp256k1 from being shrunk away (which silently disabled the Nostr update channel entirely). TV responsiveness. Typing in search ANR'd and the app was force-finished: the editor re-set inputType on every recomposition, and setInputType restarts the IME's input connection, which asks Compose for a focus search, which forced the whole results grid through a beyond-bounds layout pass — per keystroke. Reproduced on an emulator and confirmed on a real Android TV device blocking the main thread past six seconds, repeatedly, for a 10.7s frame. Playback correctness. Progress reports now carry the stream that produced them; a stale tick from the previous episode was being attributed to the newly selected one, marking it watched instantly. That is why picking episode 2 jumped Continue Watching to 3 and why re-opening an episode resumed at 0:00. Watched episodes are recorded rather than inferred from the episode number, so a skipped episode is now visibly skipped. Kiwi plays its embed again. Preferring its direct CDN renditions was measured on a Galaxy S25 against a live episode: 1080p, 720p and 360p all failed in turn and only the embed played, costing ~84 seconds of dead air. Also: next-episode sources are preheated on mobile (never on TV, where the resolver WebView has been seen taking the hardware decoder from the playing video); AniList pacing no longer throttles a cold start that has observed no budget, which was timing out the home screen; a configurable skip button (85s default); and per-server resolve telemetry rolled up by the diagnostics worker into /api/admin/source-health.
1 parent 3f8d786 commit 5997a10

278 files changed

Lines changed: 3196 additions & 1560 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -120,14 +120,30 @@ sources. Availability can vary by title, language, region, and provider uptime.
120120
</a>
121121
</p>
122122

123-
For a TV or Fire TV device, download the APK with a TV browser/Downloader app or transfer it
124-
from another device, then open it with the system package installer.
123+
On a TV or Fire TV device, download the **TV build** with a TV browser/Downloader app or transfer
124+
it from another device, then open it with the system package installer.
125+
126+
### Phones and tablets
127+
128+
| APK | Best for |
129+
| --- | --- |
130+
| [Anilili.apk](https://github.com/kompoti121/Anilili/releases/latest/download/Anilili.apk) | **Recommended.** Universal build, runs on every phone and tablet. |
131+
| [Anilili_arm64-v8a.apk](https://github.com/kompoti121/Anilili/releases/latest/download/Anilili_arm64-v8a.apk) | Smaller download for modern 64-bit devices. |
132+
| [Anilili_armeabi-v7a.apk](https://github.com/kompoti121/Anilili/releases/latest/download/Anilili_armeabi-v7a.apk) | Older 32-bit devices. |
133+
134+
### Android TV, Google TV and Fire TV
125135

126136
| APK | Best for |
127137
| --- | --- |
128-
| [Anilili.apk](https://github.com/kompoti121/Anilili/releases/latest/download/Anilili.apk) | **Recommended.** Universal build for phones, tablets, Android TV, and Fire TV. |
129-
| [Anilili_arm64-v8a.apk](https://github.com/kompoti121/Anilili/releases/latest/download/Anilili_arm64-v8a.apk) | Most modern 64-bit phones and TV devices. |
130-
| [Anilili_armeabi-v7a.apk](https://github.com/kompoti121/Anilili/releases/latest/download/Anilili_armeabi-v7a.apk) | Older 32-bit Android and Fire OS devices. |
138+
| [Anilili_tv.apk](https://github.com/kompoti121/Anilili/releases/latest/download/Anilili_tv.apk) | **Recommended.** Universal TV build. |
139+
| [Anilili_tv_arm64-v8a.apk](https://github.com/kompoti121/Anilili/releases/latest/download/Anilili_tv_arm64-v8a.apk) | 64-bit TV boxes and newer Fire TV sticks. |
140+
| [Anilili_tv_armeabi-v7a.apk](https://github.com/kompoti121/Anilili/releases/latest/download/Anilili_tv_armeabi-v7a.apk) | 32-bit Fire OS sticks. |
141+
142+
The TV build is the same app tuned for the living room: it drops Google Cast (a TV is where a cast
143+
*ends up*, and Fire TV's Play Services cannot serve the Cast module at all), which makes it about
144+
1 MB smaller and removes a class of start-up failure. Both builds share an application id, so an
145+
existing install updates in place either way — and the in-app updater keeps each device on its own
146+
build.
131147

132148
**Compatibility:** Android 5.1 / Fire OS 5 (API 22) or newer.
133149

@@ -169,15 +185,24 @@ On Windows:
169185
gradlew.bat assembleDebug
170186
```
171187

172-
Debug and release builds produce a universal APK plus `arm64-v8a` and `armeabi-v7a` variants.
173-
The in-app updater depends on these release asset names:
188+
The app builds in two product flavors on one codebase — `mobile` and `tv` — and each produces a
189+
universal APK plus `arm64-v8a` and `armeabi-v7a` variants:
174190

175-
- `Anilili.apk`
176-
- `Anilili_arm64-v8a.apk`
177-
- `Anilili_armeabi-v7a.apk`
191+
```bash
192+
./gradlew assembleMobileRelease assembleTvRelease
193+
```
194+
195+
The in-app updater depends on these six release asset names:
178196

179-
The underscore before the ABI is intentional. It keeps the universal APK first in GitHub's asset
180-
ordering so older app versions do not accidentally download an incompatible architecture build.
197+
- `Anilili.apk`, `Anilili_arm64-v8a.apk`, `Anilili_armeabi-v7a.apk` (phones and tablets)
198+
- `Anilili_tv.apk`, `Anilili_tv_arm64-v8a.apk`, `Anilili_tv_armeabi-v7a.apk` (TV)
199+
200+
The underscore before the ABI is intentional. It keeps the universal phone APK first in GitHub's
201+
asset ordering so older app versions do not accidentally download an incompatible build. **Upload
202+
the phone assets before the TV ones:** the GitHub API returns assets in upload order, and updaters
203+
from 0.1.33–0.1.55 take the first name containing their ABI, which would otherwise match a `_tv`
204+
asset. From 0.1.56 the updater filters by form factor first and only falls back to the phone
205+
family when a release has no TV assets.
181206

182207
### Publishing an update (every version bump)
183208

@@ -186,19 +211,25 @@ manifest (fallback, used when GitHub is unreachable or the repo is gone). Both m
186211
on every release so they never disagree:
187212

188213
1. Bump `appVersionCode` and `appVersionName` in `app/build.gradle.kts`.
189-
2. Build the release APKs: `./gradlew assembleRelease` (produces `Anilili.apk`,
190-
`Anilili_arm64-v8a.apk`, `Anilili_armeabi-v7a.apk`).
191-
3. Create/edit the rolling GitHub release with the three APKs attached under the names above.
214+
2. Build the release APKs: `./gradlew assembleMobileRelease assembleTvRelease` (produces the six
215+
assets listed above).
216+
3. Create/edit the rolling GitHub release with all six APKs attached, phone assets uploaded first.
192217
4. Publish the same release to Nostr:
193218

194219
```bash
195220
scripts/.venv/Scripts/python scripts/nostr_update.py publish \
196-
--version 0.1.54 \
221+
--version 0.1.56 \
197222
--apk-url "https://github.com/kompoti121/Anilili/releases/latest/download/Anilili.apk" \
223+
--tv-apk-url "https://github.com/kompoti121/Anilili/releases/latest/download/Anilili_tv.apk" \
198224
--changelog "What changed in this release" \
199-
--size-bytes 12345678
225+
--size-bytes 12345678 \
226+
--tv-size-bytes 12345678
200227
```
201228

229+
`--apk-url` must stay the **phone** build: installs published before the flavor split read only
230+
that field. The TV URL rides in a `variants` object that older apps ignore. The script warns if
231+
you omit `--tv-apk-url`, which would send Android TV devices the phone build.
232+
202233
Verify what apps will see with `scripts/nostr_update.py fetch`.
203234

204235
**If GitHub suspends the account or removes the repo:** upload the release APKs to any mirror

app/build.gradle.kts

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ val diagnosticBuildSha = providers.environmentVariable("GITHUB_SHA").orNull
1818
isIgnoreExitValue = true
1919
}.standardOutput.asText.get().trim()
2020
}.getOrDefault("unknown").ifBlank { "unknown" }
21-
val appVersionCode = 55
22-
val appVersionName = "0.1.54"
21+
val appVersionCode = 57
22+
val appVersionName = "0.1.56"
2323
val diagnosticsUploadUrl = providers.gradleProperty("diagnosticsUploadUrl").orNull
2424
?: providers.environmentVariable("DIAGNOSTICS_UPLOAD_URL").orNull
2525
?: "https://anilili-diagnostics.anilili.workers.dev"
@@ -30,7 +30,7 @@ val keystoreProperties = Properties().apply {
3030
}
3131

3232
android {
33-
namespace = "com.miruronative"
33+
namespace = "com.anilili"
3434
compileSdk = 36
3535
buildToolsVersion = "35.0.1"
3636

@@ -78,6 +78,26 @@ android {
7878
}
7979
}
8080

81+
// One codebase, two shipped apps. The split is a build variant rather than a fork: every
82+
// defect found so far (the download-scheduler crash, the progress-attribution race, the
83+
// AniList pacing regression) lived in shared logic and would have had to be fixed twice in
84+
// two repositories. What genuinely differs is what each form factor should even contain —
85+
// Google Cast is meaningless on a TV that *is* the cast target, and on Fire TV its Play
86+
// Services shell throws on every service start ("No acceptable module ... dynamite").
87+
//
88+
// applicationId is deliberately identical across flavors: changing it would orphan every
89+
// existing install from its updates and its saved library.
90+
flavorDimensions += "form"
91+
productFlavors {
92+
create("mobile") {
93+
dimension = "form"
94+
isDefault = true
95+
}
96+
create("tv") {
97+
dimension = "form"
98+
}
99+
}
100+
81101
compileOptions {
82102
isCoreLibraryDesugaringEnabled = true
83103
sourceCompatibility = JavaVersion.VERSION_17
@@ -121,24 +141,38 @@ android {
121141
// GitHub orders assets by name: '.' sorts before '_', so "Anilili.apk" (universal,
122142
// runs on every ABI) must precede "Anilili_<abi>.apk". Never name splits with '-';
123143
// '-' sorts before '.' and legacy TVs would fetch an incompatible split.
144+
//
145+
// The mobile flavor keeps the historical names exactly, because every install out
146+
// there is already looking for them. TV assets take an extra "_tv" segment, which
147+
// sorts after all of the mobile names, so a legacy updater still lands on the
148+
// universal mobile APK rather than a build meant for a different form factor.
149+
val formSuffix = if (flavorName == "tv") "_tv" else ""
124150
output.outputFileName = if (abi == null) {
125-
"Anilili$buildTypeSuffix.apk"
151+
"Anilili$buildTypeSuffix$formSuffix.apk"
126152
} else {
127-
"Anilili${buildTypeSuffix}_$abi.apk"
153+
"Anilili$buildTypeSuffix${formSuffix}_$abi.apk"
128154
}
129155
}
130156
}
131157
}
132158

133-
val archiveReleaseMapping by tasks.registering(Copy::class) {
134-
dependsOn("minifyReleaseWithR8")
135-
from(layout.buildDirectory.file("outputs/mapping/release/mapping.txt"))
136-
into(layout.buildDirectory.dir("outputs/mapping-archive"))
137-
rename("mapping.txt", "mapping-v$appVersionName-$appVersionCode-$diagnosticBuildSha.txt")
138-
}
139-
140-
tasks.matching { it.name == "assembleRelease" }.configureEach {
141-
finalizedBy(archiveReleaseMapping)
159+
// Crash reports arrive obfuscated, so every shipped build's mapping has to be kept. With the
160+
// mobile/TV flavors there are two of them per release, and the flavor has to be in the filename
161+
// or the second one silently overwrites the first — a report from a TV would then be deobfuscated
162+
// against the phone build and produce plausible, wrong class names.
163+
listOf("mobile", "tv").forEach { flavor ->
164+
val variant = "${flavor}Release"
165+
val archiveTask = tasks.register<Copy>("archive${variant.replaceFirstChar(Char::uppercase)}Mapping") {
166+
dependsOn("minify${variant.replaceFirstChar(Char::uppercase)}WithR8")
167+
from(layout.buildDirectory.file("outputs/mapping/$variant/mapping.txt"))
168+
into(layout.buildDirectory.dir("outputs/mapping-archive"))
169+
rename(
170+
"mapping.txt",
171+
"mapping-$flavor-v$appVersionName-$appVersionCode-$diagnosticBuildSha.txt",
172+
)
173+
}
174+
tasks.matching { it.name == "assemble${variant.replaceFirstChar(Char::uppercase)}" }
175+
.configureEach { finalizedBy(archiveTask) }
142176
}
143177

144178
dependencies {
@@ -167,7 +201,9 @@ dependencies {
167201
implementation(libs.androidx.media3.datasource)
168202
implementation(libs.androidx.media3.datasource.cronet)
169203
implementation(libs.androidx.media3.database)
170-
implementation(libs.androidx.media3.cast)
204+
// Cast is mobile-only. It also drags in play-services-cast-framework and mediarouter, so
205+
// scoping it here is what actually makes the TV build smaller, not just tidier.
206+
"mobileImplementation"(libs.androidx.media3.cast)
171207
implementation(libs.androidx.media3.transformer)
172208
implementation(libs.play.services.cronet)
173209
implementation(libs.androidx.appcompat)

app/proguard-rules.pro

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,26 @@
66
-keepattributes *Annotation*, InnerClasses
77
-dontnote kotlinx.serialization.**
88
-keepclassmembers class **$$serializer { *; }
9-
-keepclasseswithmembers class com.miruronative.data.model.** {
9+
-keepclasseswithmembers class com.anilili.data.model.** {
1010
kotlinx.serialization.KSerializer serializer(...);
1111
}
12-
-keep,includedescriptorclasses class com.miruronative.data.model.**$$serializer { *; }
13-
-keep class com.miruronative.data.model.** { *; }
12+
-keep,includedescriptorclasses class com.anilili.data.model.**$$serializer { *; }
13+
-keep class com.anilili.data.model.** { *; }
1414

1515
# OkHttp
1616
-dontwarn okhttp3.**
1717
-dontwarn okio.**
18+
19+
# secp256k1 (Nostr update-manifest signature check).
20+
#
21+
# Only the `Secp256k1` facade is referenced from Kotlin; it reaches its JNI backend reflectively,
22+
# so R8 saw the implementation as unreachable and shrank it away. The facade's static initializer
23+
# then threw ExceptionInInitializerError, which `runCatching { ... }.getOrDefault(false)` swallowed
24+
# into "signature invalid" — the Nostr fallback channel was inert in every release build while
25+
# still shipping ~1.3 MB of .so per ABI. Verified on an Android TV emulator: the release mapping
26+
# contained `fr.acinq.secp256k1.Secp256k1` and nothing else from that package.
27+
-keep class fr.acinq.secp256k1.** { *; }
28+
-keepclasseswithmembernames,includedescriptorclasses class * {
29+
native <methods>;
30+
}
31+
-dontwarn fr.acinq.secp256k1.**

app/src/androidTest/java/com/miruronative/data/remote/AllAnimeCaptchaUiTest.kt renamed to app/src/androidTest/java/com/anilili/data/remote/AllAnimeCaptchaUiTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
package com.miruronative.data.remote
1+
package com.anilili.data.remote
22

33
import androidx.test.core.app.ActivityScenario
44
import androidx.test.ext.junit.runners.AndroidJUnit4
5-
import com.miruronative.MainActivity
6-
import com.miruronative.diagnostics.WebViewProcessController
5+
import com.anilili.MainActivity
6+
import com.anilili.diagnostics.WebViewProcessController
77
import java.util.concurrent.Executors
88
import java.util.concurrent.TimeUnit
99
import kotlinx.coroutines.runBlocking

app/src/androidTest/java/com/miruronative/data/remote/AllAnimeWebViewTest.kt renamed to app/src/androidTest/java/com/anilili/data/remote/AllAnimeWebViewTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.miruronative.data.remote
1+
package com.anilili.data.remote
22

33
import android.annotation.SuppressLint
44
import android.graphics.Bitmap
@@ -14,8 +14,8 @@ import android.webkit.WebSettings
1414
import android.webkit.WebViewClient
1515
import androidx.test.core.app.ActivityScenario
1616
import androidx.test.ext.junit.runners.AndroidJUnit4
17-
import com.miruronative.MainActivity
18-
import com.miruronative.data.model.StreamItem
17+
import com.anilili.MainActivity
18+
import com.anilili.data.model.StreamItem
1919
import java.time.Duration
2020
import java.util.concurrent.CountDownLatch
2121
import java.util.concurrent.TimeUnit

app/src/androidTest/java/com/miruronative/data/remote/AniDbAppPlaybackTest.kt renamed to app/src/androidTest/java/com/anilili/data/remote/AniDbAppPlaybackTest.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.miruronative.data.remote
1+
package com.anilili.data.remote
22

33
import android.net.Uri
44
import androidx.annotation.OptIn
@@ -13,9 +13,9 @@ import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
1313
import androidx.test.core.app.ApplicationProvider
1414
import androidx.test.ext.junit.runners.AndroidJUnit4
1515
import androidx.test.platform.app.InstrumentationRegistry
16-
import com.miruronative.data.model.Media
17-
import com.miruronative.data.model.MediaTitle
18-
import com.miruronative.data.model.StreamItem
16+
import com.anilili.data.model.Media
17+
import com.anilili.data.model.MediaTitle
18+
import com.anilili.data.model.StreamItem
1919
import java.time.Duration
2020
import java.util.concurrent.CountDownLatch
2121
import java.util.concurrent.TimeUnit

app/src/androidTest/java/com/miruronative/data/remote/KickAssAnimePlaybackTest.kt renamed to app/src/androidTest/java/com/anilili/data/remote/KickAssAnimePlaybackTest.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.miruronative.data.remote
1+
package com.anilili.data.remote
22

33
import androidx.media3.common.C
44
import androidx.media3.common.MediaItem
@@ -10,10 +10,10 @@ import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
1010
import androidx.test.core.app.ApplicationProvider
1111
import androidx.test.ext.junit.runners.AndroidJUnit4
1212
import androidx.test.platform.app.InstrumentationRegistry
13-
import com.miruronative.data.model.Media
14-
import com.miruronative.data.model.MediaTitle
15-
import com.miruronative.data.model.StreamItem
16-
import com.miruronative.ui.watch.applyCategoryAudioPreference
13+
import com.anilili.data.model.Media
14+
import com.anilili.data.model.MediaTitle
15+
import com.anilili.data.model.StreamItem
16+
import com.anilili.ui.watch.applyCategoryAudioPreference
1717
import java.time.Duration
1818
import java.util.concurrent.CountDownLatch
1919
import java.util.concurrent.TimeUnit

app/src/androidTest/java/com/miruronative/playback/MiruroCastMediaItemConverterTest.kt renamed to app/src/androidTest/java/com/anilili/playback/MiruroCastMediaItemConverterTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.miruronative.playback
1+
package com.anilili.playback
22

33
import android.net.Uri
44
import androidx.annotation.OptIn

app/src/main/AndroidManifest.xml

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,25 @@
1515
<uses-feature android:name="android.hardware.faketouch" android:required="false" />
1616
<uses-feature android:name="android.hardware.wifi" android:required="false" />
1717

18+
<!--
19+
largeHeap: the app runs a Compose UI, an ExoPlayer, and up to two WebViews (the resolver
20+
bridge and an embed player) in one process. On a Fire TV stick the standard ceiling is
21+
192 MB, and a heap-exhaustion OutOfMemoryError lands wherever the next allocation happens
22+
to be — one crash report died inside a routine SharedPreferences write. Devices flagged
23+
low-RAM ignore the flag, so it only lifts the ceiling where there is memory to lift it to.
24+
-->
1825
<application
1926
android:name=".MiruroApp"
2027
android:allowBackup="false"
2128
android:banner="@drawable/tv_banner"
2229
android:icon="@mipmap/ic_launcher"
2330
android:label="@string/app_name"
31+
android:largeHeap="true"
2432
android:roundIcon="@mipmap/ic_launcher_round"
2533
android:supportsRtl="true"
2634
android:theme="@style/Theme.MiruroNative"
2735
android:usesCleartextTraffic="${usesCleartextTraffic}">
2836

29-
<meta-data
30-
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
31-
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
32-
3337
<activity
3438
android:name=".MainActivity"
3539
android:exported="true"
@@ -78,6 +82,18 @@
7882
</intent-filter>
7983
</service>
8084

85+
<!--
86+
EpisodeDownloadService hands Media3 a PlatformScheduler so paused downloads resume when
87+
their requirements are met again. PlatformScheduler schedules work against this
88+
JobService, and JobScheduler rejects a component the manifest never declared:
89+
"IllegalArgumentException: No such service" killed the app on the main thread whenever
90+
the download manager reconsidered its queue.
91+
-->
92+
<service
93+
android:name="androidx.media3.exoplayer.scheduler.PlatformScheduler$PlatformSchedulerService"
94+
android:exported="false"
95+
android:permission="android.permission.BIND_JOB_SERVICE" />
96+
8197
<!--
8298
MP4 exports run as long-running WorkManager work, which promotes WorkManager's own
8399
service to the foreground. From Android 14 that service must carry a type, and only
@@ -88,11 +104,6 @@
88104
android:foregroundServiceType="dataSync"
89105
tools:node="merge" />
90106

91-
<!-- Lets Android 13+ System UI move this MediaSession between the phone and Cast routes. -->
92-
<receiver
93-
android:name="androidx.mediarouter.media.MediaTransferReceiver"
94-
android:exported="true" />
95-
96107
<provider
97108
android:name="androidx.core.content.FileProvider"
98109
android:authorities="${applicationId}.fileprovider"

0 commit comments

Comments
 (0)