Miruro Playback Fails When Google Play Services Is Disabled (Cronet/GMS Dependency) #2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Miruro Playback Fails When Google Play Services Is Disabled (Cronet/GMS Dependency)
Header
AnimeApi.initHttpEngine), Miruro source (SD8)| Severity | Critical — Miruro is unplayable on all stock Fire TV / Firestick devices (Fire OS has no Google Play Services) plus any non-GMS / GMS-disabled Android device |
Summary
Miruro videos stop playing after Google Play Services is disabled, and start
playing again once it is re-enabled. The app relies on Cronet (Chromium's
network stack) to produce a Chrome-like TLS fingerprint for Miruro's
Cloudflare-protected CDNs. Cronet is loaded from Google Play Services
(
play-services-cronet, bundled viaimplementation), while the offlinefallback (
cronet-embedded) is declaredcompileOnlyand therefore isNOT packaged in the APK. When Play Services is disabled, no Cronet engine
can initialize, so Miruro requests fall back to OkHttp / the platform HTTP
stack, whose TLS fingerprint Cloudflare rejects (HTTP 400/403) — breaking
playback.
Key Findings
1. Miruro requires Cronet's TLS fingerprint
Miruro's secure pipe and its CDNs sit behind Cloudflare, which rejects
non-Chrome TLS fingerprints. The code documents this explicitly:
AnimeApi.java(Http constructor, ~line 722):2. Cronet is loaded from Google Play Services (the dependency at risk)
app/build.gradle:play-services-cronetisimplementation→ the Cronet provider isbundled, but the actual Cronet engine is delivered at runtime by Google
Play Services. If Play Services is disabled/absent, this provider has
nothing to load.
cronet-embedded(the self-contained native fallback that would NOT needGMS) is
compileOnly→ its native.sois intentionally excluded fromthe APK to keep size down.
3. Both Cronet init attempts fail without GMS
AnimeApi.initHttpEngine():Attempt 1 — auto-discovery relies on the Play Services provider:
Attempt 2 — explicit embedded native provider:
Because
cronet-embeddediscompileOnly, the native classes are missing atruntime →
NoClassDefFoundError→cronetClient == null.4. Fallback path reproduces the Cloudflare rejection
With
cronetClient == null, aforceCronetrequest degrades to the platformHttpURLConnection:The platform/OkHttp TLS fingerprint is rejected by Cloudflare (HTTP 400/403),
so the Miruro stream/pipe never resolves → no playback.
5. The
HTTP_CLIENTsettings toggle is bypassed for Miruro CDNsUser tested switching the app's HTTP client setting to "generic HTTP client"
and saw identical broken behavior while GMS was disabled. This is expected
and further confirms the root cause: for Miruro's Cloudflare-protected CDNs, the
DataSource selection ignores the user's
HTTP_CLIENTsetting and computesits own
effectiveHttpClient.AnimeView.java(video DataSource factory, ~line 1088):Consequence table (GMS disabled →
cronetClient == null):So no
HTTP_CLIENTchoice can fix Miruro while GMS is disabled — only a workingCronet engine can. This is precisely why the "generic HTTP client" test showed
the same behavior. It also means the only setting that can differ is when GMS
is enabled and Cronet exists: then Miruro is forced onto Cronet regardless
of the toggle.
6. Why re-enabling GMS "fixes" it
Re-enabling Play Services restores the Cronet provider → Attempt 1 succeeds →
cronetClient != null→ Miruro requests use the Chrome-like TLS fingerprint →Cloudflare accepts them → playback works again. This exactly matches the
reported off → broken, on → working behavior.
7. Confirming logcat evidence (GMS disabled)
Captured while GMS was disabled and the HTTP client was switched to "generic",
which triggered a
setHttpClient→initHttpEnginere-init:This is the exact two-attempt failure chain the analysis predicted:
GooglePlayServices not available due to error 3(error code 3 =SERVICE_DISABLED) → "All available Cronetproviders are disabled."
NoClassDefFoundError/ClassNotFoundExceptionfororg.chromium.net.impl.NativeCronetProvider,because the
compileOnlycronet-embeddednative lib is not present inthe APK's DexPathList. This is the direct proof of Finding #2/#3.
cronetClient == null→ "CF-protected CDNs will return 403."Secondary (non-root-cause) noise: the
java.net.SocketException: Socket closedandjava.io.IOException: Canceledentries occur because switching theHTTP client setting calls
AnimeApi.initHttpEngine(), which runsdispatcher().cancelAll()and shuts down the OkHttp executor — cancelling thein-flight home-feed requests (aniwave/miruro trending) that were mid-TLS
handshake. These are a benign side effect of the live re-init, not the Miruro
playback failure.
8. Visible symptom (user-facing)
With GMS disabled, playing a Miruro title shows an indefinite "LOADING DATA"
spinner on a black screen with no playback controls (see reporter screenshot,
Pixel_4_XL emulator). The stream URL never resolves (Cloudflare rejects the
non-Cronet requests), so ExoPlayer never receives a playable manifest → the
player UI (menu / playback buttons) is never built and the spinner never
clears.
With GMS enabled, the same title loads the manifest, shows the normal playback
menu buttons, and plays — matching the reporter's description.
This confirms the failure is at stream resolution, before ExoPlayer can
render anything, which is exactly what a null
cronetClient+ Cloudflare 400/403produces.
9. Version regression timeline (git-traced)
Traced via
app/build.gradlehistory across the 5.x tags:play-services-cronet(GMS)compileOnly cronet-embeddedfallback819a6644)compileOnly)compileOnly)Conclusion on "was this introduced after 5.14.1?": No.
introduced in v5.14.0 via commit
819a6644("feat: implement triple DataSource switching (OkHttp/Generic/Cronet)"),
which added the media3 Cronet DataSource and forced Miruro CDNs onto Cronet.
This is at/before 5.14.1 — so 5.14.1 is already affected.
compileOnly cronet-embeddedline was added later, in v5.14.2. BecausecompileOnlydoes not package the native library, it never provided a realoffline fallback; it only causes the second init attempt to fail with
NoClassDefFoundError. So 5.14.2+ did not fix (nor introduce) the core issue —it merely added a non-functional fallback attempt.
play-services-cronetitself predates all of this (present since ≥ 5.13.0),but was harmless until 5.14.0 wired Miruro playback through Cronet.
Test Result — Cronet-forcing Disabled (2026-07-04)
To validate the root cause, both Cronet-forcing layers for Miruro were
temporarily disabled behind TEST-ONLY toggles (on branch
experiment/miruro-cronet-play-services-fix), so the user'sHTTP_CLIENTsetting is honored instead of being overridden to Cronet:
AnimeView.javavideo DataSource —FORCE_CRONET_FOR_MIRURO_CDN = false(skips the CF-CDN override in the
videoDataSourceFactory).AnimeView.javaMiruro secure pipe —MIRURO_PIPE_FORCE_CRONET = false(the pipe fetch that resolves the stream URL was independently forcing
Cronet; it now honors
Conf.HTTP_CLIENTtoo).Result: With GMS disabled and Cronet-forcing off, Miruro plays
using the non-Cronet HTTP client. The build compiled cleanly and playback
worked on the emulator without Google Play Services.
What this changes about the root cause
The original Findings #4–#6 predicted that any non-Cronet client would be
rejected by Cloudflare (400/403), implying only a working Cronet engine could
play Miruro. The test disproves the absolute form of that claim: at least
for the CDN/pipe paths exercised in this test, OkHttp / the platform client
can reach Miruro without Cronet.
Revised understanding: the GMS-disabled failure was caused primarily by the
forced-Cronet routing degrading to a broken fallback, not by an inherent
Cloudflare requirement for a Chrome TLS fingerprint on every Miruro request.
When the code forces Cronet and Cronet is null, the fallback path (and/or its
header/config assumptions) fails — whereas letting OkHttp handle the request
normally succeeds.
Implication for the fix
This reopens a cheaper alternative to shipping
cronet-embedded(which the user has confirmed is undesirable due to the ~20–30 MB APK size
increase): instead of bundling Cronet, stop force-routing Miruro CDNs through
Cronet when
cronetClient == nulland let the existing OkHttp/platformclient handle them. This avoids the GMS dependency and avoids the APK bloat.
Still pending: the user is testing all Miruro providers (ally, bee, pewe,
bonk, hop, kiwi, etc.) with Cronet-forcing disabled to confirm none of them
actually require the Chrome TLS fingerprint. If some providers still need
Cronet, a targeted per-provider decision (rather than a blanket force) would be
the correct fix.
Final Resolution (2026-07-05) — SUPERSEDED (see "Device Test — 2026-07-05" below)
Fix implemented (Option A): honor the user's
HTTP_CLIENTsetting; forceCronet only for the Kiwi CDN when a Cronet engine exists.
Following the 2026-07-04 test that proved Miruro plays over OkHttp/platform
without Cronet, the two blanket Cronet-forcing layers were removed and replaced
with the user's setting. Testing across Miruro providers confirmed only the
Kiwi CDN (
owocdn.top,uwucdn.top,nekostream.site,kwik.cx) stillneeds Cronet's Chrome TLS fingerprint. This narrow exception is applied only
when
AnimeApi.cronetClient != null; otherwise it falls back to the user'sHTTP_CLIENTsetting (no broken forced-null path).Changes (in
AnimeView.java)override.
effectiveHttpClientnow starts fromConf.HTTP_CLIENTand is onlybumped to Cronet (
2) for Kiwi URLs if a Cronet engine is available; ifKiwi is detected but Cronet is null, it logs and falls back to the setting.
new AnimeApi.Http(pipeUrl, false)(forceCronet=false), honoring the setting.Browser-like headers + WebView cookie forwarding remain to satisfy Cloudflare.
Outcome
cronet-embeddedremainscompileOnly; we did not bundlethe embedded Chromium net stack.
Source isolation note
Per the source-isolation rule, these changes affect only the Miruro (SD7/SD8)
paths and the Kiwi exception within them; no other source's CDN resolution was
touched.
Device Test — 2026-07-05 (real GMS-disabled Android TV box) — CORRECTED FINDINGS
Setup: A physical Android TV box at
192.168.100.106with Google PlayServices disabled, deliberately configured to emulate a stock Fire TV /
Firestick (no GMS). A debug build was deployed via
./deploy.sh. The deployedAnimeView.javawas the TEST-TOGGLE variant — both Cronet-forces disabled(
FORCE_CRONET_FOR_MIRURO_CDN = false,MIRURO_PIPE_FORCE_CRONET = false) — sothe user's
HTTP_CLIENTsetting is fully honored for every Miruro request (noCronet forcing anywhere).
Results (Miruro playback, GMS disabled):
HTTP_CLIENTsettingHttpURLConnectionThis corrects the earlier conclusions. The 2026-07-04 test and the
"RESOLVED" section assumed OkHttp and Generic behave the same. They do not:
accepted by Cloudflare on the Miruro pipe/CDNs.
HttpURLConnectionhas adifferent (plain-JDK) TLS fingerprint and no DoH, and Cloudflare does not
accept it for the Miruro pipe → the stream URL never resolves → indefinite
"LOADING DATA".
Revised root-cause conclusion
The original IA premise — "Miruro requires Cronet" — is wrong for GMS-less
devices. On a real Fire-TV-like device, OkHttp is the ONLY client that both
(a) initializes without GMS and (b) passes Cloudflare. Forcing Cronet on such
devices is the worst option (it is unavailable), and Generic is the weakest
(no DoH, non-Chrome TLS).
Corrected fix direction (proposed, not yet implemented)
For the Miruro secure pipe and CF-protected CDNs, the correct behavior on
GMS-less devices is to force OkHttp (not Cronet, not Generic) — i.e. prefer
AnimeApi.httpClientwhencronetClient == null, rather than degrading to theplatform
HttpURLConnectionor honoring a Generic setting that Cloudflarerejects. This keeps Miruro playable on Fire TV without APK bloat.
Regression History — Where It Broke and Where It Worked (git-traced)
This section preserves the exact commit history so the regression is not lost.
All commits are relative to tag
v5.12.0(commit1ddafb57, dated2026-05-26), because every relevant commit sits after 5.12.0 (they all
git describeasv5.12.0-NNN-g...).✅ Last known-good state — v5.12.0 (
1ddafb57)In 5.12.0 the HTTP layer was simple and honored the user's setting:
AnimeApi.Httphad only one constructor —Http(String url)— with noforceCronetargument. No request could force Cronet.initHttpEngine()built Cronet only whenConf.HTTP_CLIENT == 2; otherwiseCronet was never touched (
if (Conf.HTTP_CLIENT==2){ ... build Cronet ... }).Conf.HTTP_CLIENT-driven:>0→ platform/Cronet
HttpURLConnection,==0→ OkHttp. No CDN-based override existed.AnimeViewhad noeffectiveHttpClientlogic and no CDN allow-list.Because nothing forced Cronet, Miruro playback did not depend on GMS at 5.12.0.
❌ Where the Cronet dependency was introduced (all after v5.12.0)
819a6644v5.12.0-…AnimeView.3ad23aeev5.12.0-144effectiveHttpClientinAnimeView, overridingConf.HTTP_CLIENTand forcing Cronet for the Miruro/CF CDN allow-list (mewstream.buzz,nekostream.site,ultracloud.cc,krussdomi.com, etc.).25a87cd5Http(url, forceCronet)(and later 3-arg) overload used to force Cronet on the secure pipe (new AnimeApi.Http(pipeUrl, true)).The net effect of
3ad23aee+25a87cd5: Miruro requests were force-routedthrough Cronet, which requires Google Play Services. On GMS-less devices
(Fire TV / Firestick) Cronet cannot initialize, so the forced path degraded to a
broken fallback → Miruro unplayable. This is the regression.
✅ How this current work fixes it (branch
fix/firestick-black-screen-remove-visibility-toggle)The fix removes the blanket forced-Cronet routing and restores honoring the
user's setting, with one narrow, availability-guarded exception:
AnimeViewvideo DataSource — the CF-CDN allow-list that forcedeffectiveHttpClient = 2was replaced.effectiveHttpClientnow starts fromConf.HTTP_CLIENT; only the Kiwi CDN (owocdn.top,uwucdn.top,nekostream.site,kwik.cx) is bumped to Cronet and only ifAnimeApi.cronetClient != null. If Kiwi is seen but Cronet is unavailable(GMS-less), it logs and falls back to the user's setting.
new AnimeApi.Http(pipeUrl, true)→new AnimeApi.Http(pipeUrl, false), so the pipe fetch honorsConf.HTTP_CLIENTinstead of forcing Cronet. Browser-like headers + WebViewcookie forwarding remain to satisfy Cloudflare.
Reverting the diff shows exactly what changed (from
git diff HEAD):Caveat (see Device Test above): the deployed test build additionally set
FORCE_CRONET_FOR_MIRURO_CDN = falseandMIRURO_PIPE_FORCE_CRONET = false.Under that build, OkHttp plays Miruro but Generic gets stuck. So the final
production fix should also ensure the CF/pipe paths prefer OkHttp when Cronet
is null, rather than allowing the Generic platform client to be used there.
The Cronet error in logcat is benign noise (clarification)
Even when Generic (or OkHttp) is selected, the logcat still shows:
This is expected and NOT the cause of the Generic failure.
initHttpEngine()always attempts to build a Cronet engine regardless of the
HTTP_CLIENTsetting (see
AnimeApi.java~line 617: "Always initialize Cronet regardless ofHTTP_CLIENT preference"). On a GMS-less box both Cronet attempts fail and log
this scary line at init time — but Generic requests never touch Cronet, so the
message is misleading. The
Cronet Init FAILEDline is fired at engineinit, not on a failed playback request.
How the "HTTP Client" Setting Works (Reference)
This section documents how the Settings → HTTP Client option behaves, end to
end, from the JS toggle down to the Java networking layer. Captured here because
it is valuable context that keeps getting re-derived during investigations.
The three options
The setting is stored as
Conf.HTTP_CLIENT(anint) and the UI labels comefrom
pb.cfghttpclient_nameinapp/src/main/assets/view/modules/18-playback.js:1.1.1.1)java.net.HttpURLConnectionWhat each option actually does
DNS-over-HTTPS. Its TLS handshake is generally accepted by Cloudflare, and DoH
avoids DNS-based blocks. On GMS-less devices this is the only client that
plays Miruro.
HttpURLConnection. A lightweightcompatibility fallback for when OkHttp misbehaves on a specific device/ROM. It
has a different, plain TLS fingerprint and no DoH, so Cloudflare-
protected CDNs (like Miruro) are more likely to reject it. If constructing the
HttpURLConnectionfails, the code silently falls back to OkHttp.TLS fingerprint — the best choice for bypassing Cloudflare. However it
requires Google Play Services (
play-services-cronet) or the embeddedCronet native library (
cronet-embedded, currentlycompileOnlyso NOT inthe APK). On Fire TV / GMS-disabled boxes, Cronet cannot initialize and is
effectively unavailable.
End-to-end flow (who calls what)
18-playback.js, selecting the option updatespb.cfg_data.httpclientand calls_JSAPI.setHttpClient(value).AnimeView.JSViewApi.setHttpClient(int val)runs. Itskips a no-op re-init (if the value hasn't changed it returns early — this
guard was added to avoid the Firestick "cache is closed" boot crash), then
sets
Conf.HTTP_CLIENT = valand callsAnimeApi.initHttpEngine(activity).AnimeApi.initHttpEngine()tears down the old engines andrebuilds them. Note: it always tries to build Cronet as a fallback
engine, regardless of the chosen option (this is why the Cronet init error
appears even under OkHttp/Generic on GMS-less boxes — see the clarification
above). It then builds the OkHttp
bootstrapClient, the DoH client, and setshttpClient(with or without DoH based onConf.USE_DOH).AnimeApi.Http:Conf.HTTP_CLIENT > 0→ it opens a platformHttpURLConnection(Generic). Only if
HTTP_CLIENT == 2andcronetClient != nulldoes ituse Cronet's
HttpURLConnection. Any failure falls back to OkHttp.Conf.HTTP_CLIENT == 0→ it uses OkHttp (req/httpClient) directly.AnimeViewcomputes aneffectiveHttpClientstarting fromConf.HTTP_CLIENT. Historically this wasoverridden to force Cronet for Miruro/CF CDNs (the source of this whole
investigation). The current in-progress direction is to honor the setting and
only apply a narrow, availability-checked exception.
Practical guidance
Miruro there. Generic and Cronet both fail.
misbehaves on a specific device; expect Cloudflare-protected sources to break.
Cronet lib) is present; gives the strongest Cloudflare bypass via Chrome TLS.
Comparison
compileOnly, not in APK)Failure Scenarios
Fire TV / Firestick (PRIMARY IMPACT): Fire OS is a forked Android that
ships without Google Play Services by default. Therefore stock Fire TV
and Firestick devices have no
play-services-cronetruntime,cronetClientis always null, and Miruro is unplayable out of the box for essentially the
entire Fire TV user base (unless they've sideloaded microG/GMS, which most
have not). The GMS-disabled emulator test reproduced exactly the stock Fire TV
condition. This elevates the issue from an edge case to a platform-wide
outage on a primary target device for a TV app.
GMS-disabled / degraded devices: Any device where the user disables Play
Services, or where GMS is stale/broken, loses Miruro entirely.
Non-GMS devices (AOSP TV boxes, de-Googled ROMs): Same failure — Miruro
never works because there is no embedded Cronet fallback shipped.
Silent-ish failure: The error is only visible in logcat
(
Cronet Init FAILED completely/forceCronet FALLBACK); the user justsees a stream that won't play.
Recommended Actions
Immediate (diagnosis confirmation)
AnimeApitag and confirm the presenceof
Cronet Init FAILED completely. CF-protected CDNs will return 403.whilePlay Services is disabled.
Short-term (fix the fallback)
compileOnlytoimplementationso the self-contained native library ships in the APK: This makes Attempt 2 actually succeed when GMS is unavailable, removing
the hard dependency on Google Play Services for Miruro playback.
a full Chromium net stack native lib, tens of MB). Consider ABI splits /
per-architecture APKs to mitigate.
-maximumremovedandroidloglevelProGuard rule issue noted inthe gradle comment is handled if switching to
implementation.Long-term (architecture)
flavor split (GMS vs. non-GMS) to balance APK size against reliability.
cronetClient == nulland a Miruroplayback is attempted, so the failure is not silent.
Files Involved
app/src/main/java/com/amarullz/androidtv/animetvjmto/AnimeApi.javainitHttpEngine()Cronet init +Httpctor fallback logicapp/build.gradleplay-services-cronet= implementation,cronet-embedded= compileOnly)electron/src/data/datasources/miruro-pipe.jsRelated Commits
620a28775b48cac5cb808caf770be4d1a8b97a78Assumptions
res/xml/network_security_config.xmlor custom TLS/TrustManager overridesexist (verified: none found in the
animetvjmtopackage), so TLS behavior ispurely a function of which HTTP engine (Cronet vs OkHttp/platform) is used.