Automated pipeline that patches the F1TV Android TV app to enable UHD/4K playback on any device. (only tested on my own Nvidia Shield TV Pro)
- Checks APKPure every 3 hours for new F1TV Android TV releases
- Downloads the app bundle — Google Play primary (arm64 native via NVIDIA Shield profile), APKPure and APKMirror as fallbacks
- Patches the smali to unlock 4K on any device (UHD/device whitelist bypass, the 1.5× display-size cap, the HDR-capability gate that unlocks the 2160p tier, and decoder/render tweaks — see Applied patches)
- Signs all APKs with a consistent keystore
- Publishes the patched bundle as a GitHub Release
- Notifies via Pushover when a new patch is ready (or if it fails)
How 4K gets unlocked (and why stock skips the Shield). F1TV picks a stream manifest from the
x-f1-device-infoheader. The Android app sendsdevice=android_tv, which returns the WidevineHDR-UHD-CMAF-WVmanifest where 2160p exists only as HDR; SDR is hard-capped at 1620p server-side. ClearVR only requests the 2160p tier if the content's HDR type is in both what the EGL renderer supports and what the display reports (Display.getHdrCapabilities()). F1's UHD is HLG; a panel that reports only HDR10 (like most setups behind an NVIDIA Shield) makes ClearVR reject HLG upstream and fall back to 1620p SDR. The pipeline forces the EGL HDR advertise and spoofs the display-capability check, so the core serves the 2160p tiles; the device's video pipeline then plays them as HDR10 or a clean 4K SDR downconvert (the same path YouTube HLG uses). Needs the arm64 build — see Verify 4K is working. Genuinely HLG-capable devices get full HDR; HDR10-only panels get accurate 4K. An opt-inF1TV_PQ_REROUTEcan push some setups to true HDR10 output — see Build options.
- On your Android TV, go to Settings > Device Preferences > About
- Scroll to Build and click it 7 times to enable Developer Options
- Go back to Settings > Device Preferences > Developer Options
- Enable USB debugging (and ADB over network if installing wirelessly)
- Note the IP address shown under Settings > Network & Internet, or Device Preferences > About > Status
Install ADB on your computer (download platform-tools) and add it to your PATH.
Via USB:
# Connect your Android TV via USB cable, then:
adb devices # Confirm it shows up — approve the prompt on your TVVia WiFi:
adb connect 192.168.1.100:5555 # Replace with your TV's IP
# Approve the connection prompt on your TVThen download f1tv-uhd-patched.apkm from the latest release and install:
# Uninstall the original F1TV first (required — different signing key)
adb uninstall com.formulaone.production
# Use the install script (auto-extracts and auto-detects device config)
./scripts/install.sh f1tv-uhd-patched.apkm
# With ADB over WiFi:
./scripts/install.sh f1tv-uhd-patched.apkm 192.168.1.100:5555Or install manually:
# Unzip the bundle
mkdir f1tv && cd f1tv && unzip ../f1tv-uhd-patched.apkm
# Install (most Android TVs — NVIDIA Shield, Chromecast, etc. — are arm64).
# Use the arm64_v8a split; the 32-bit armeabi_v7a native libs can't sustain 4K.
adb install-multiple base.apk \
config.arm64_v8a.apk \
config.en.apk \
config.xhdpi.apkInstall the
arm64_v8asplit, notarmeabi_v7a. The ClearVR decoder/renderer ships per ABI. On an arm64 device (NVIDIA Shield included) the 32-bit split forces ClearVR to run in 32-bit, which commonly can't allocate the 4K secure HEVC decoder — you getTM4014/ "acquireVdecResource not enough" errors.install.shpicks arm64 automatically when it's in the bundle; a bundle with onlyarmeabi_v7a(e.g. the APKPure fallback source) can't do reliable 4K on the Shield.
The install script accepts .apkm, .xapk files, or a directory of extracted APKs.
No computer needed after the initial download.
- Download
f1tv-uhd-patched.apkmfrom the latest release on your phone - Install Split APKs Installer (SAI) on your Android TV (available on Play Store)
- Transfer the
.apkmfile to your TV via:- USB drive — copy the file to a USB stick, plug it into the TV
- Send Files to TV — install this app on both your phone and TV, send the file over WiFi
- Google Drive / cloud — upload to Drive, open it from the TV's file manager
- Open SAI on the TV, select the
.apkmfile, and install
If you don't have a computer but want a one-tap solution:
- Install Bugjaeger on your Android phone
- Enable ADB over network on your TV (see prerequisites above)
- Connect Bugjaeger to your TV via its IP address
- Use Bugjaeger to install the individual APK files
The bundle contains split APKs for different device configurations. You need base.apk plus the correct splits. Split names vary by source (config.* from APKPure, split_config.* from APKMirror, com.formulaone.production.config.* from Google Play):
| Split (any prefix) | When to include |
|---|---|
*.arm64_v8a.apk |
Most modern Android TVs (NVIDIA Shield, Chromecast, etc.) |
*.armeabi_v7a.apk |
Older 32-bit devices |
*.x86.apk |
Some emulators |
*.en.apk |
English — replace en with your language code |
*.xhdpi.apk |
Standard TV density — almost always needed |
Note: You must uninstall the original F1TV app before installing the patched version (different signing key). This means you'll need to log in again.
Fork this repo and enable GitHub Actions in the Actions tab.
The pipeline uses a custom build of apkeep with an NVIDIA Shield TV device profile added to rs-google-play. This allows downloading the arm64 native variant directly from Google Play.
To set up your own:
- Fork EFForg/rs-google-play, add your device profile to
gpapi/device.properties, deletegpapi/src/device_properties.bin, commit & push - Fork EFForg/apkeep, change
Cargo.tomlto pointgpapiat your rs-google-play fork, commit & push - Tag a release (
git tag v0.18.0-shield && git push origin v0.18.0-shield) — the included workflow builds the binary automatically - Update
APKEEP_CUSTOM_TAGinpatch.ymlto match your tag
A device profile dump script is included at scripts/dump_device_props.sh — connect your Android TV via ADB and run it to generate the profile.
In Settings > Secrets > Actions, add:
| Secret | Purpose | Required |
|---|---|---|
GOOGLE_EMAIL |
Google account email for Play Store downloads | For Google Play |
GOOGLE_AAS_TOKEN |
Google AAS token (how to obtain) | For Google Play |
KEYSTORE_B64 |
Base64-encoded signing keystore (persistent key across builds) | Recommended |
KEYSTORE_PASS |
Keystore password | Recommended |
KEYSTORE_ALIAS |
Key alias | Recommended |
PUSHOVER_APP_TOKEN |
Pushover app token for notifications | Optional |
PUSHOVER_USER_KEY |
Pushover user key for notifications | Optional |
Without Google Play credentials, the pipeline falls back to APKPure (armeabi-v7a only) and APKMirror automatically.
Generate a persistent keystore:
keytool -genkeypair -keystore f1tv.keystore -storepass yourpass \
-keypass yourpass -alias f1tvpatch -keyalg RSA -keysize 2048 \
-validity 10000 -dname "CN=F1TV UHD Patch"
# Encode and add as KEYSTORE_B64 secret
base64 -w0 f1tv.keystoreWithout a persistent keystore, a new key is generated each build — you'll need to uninstall before each update.
If the automatic download fails, you can trigger the workflow manually:
- With direct URL: Go to Actions > F1TV UHD Patch > Run workflow, paste an
.apkmURL - Force rebuild: Check the "Force rebuild" option to re-patch an existing version
.github/workflows/patch.yml # CI pipeline (check, download, patch, release)
scripts/
check_version.py # APKMirror RSS parser (fallback version check)
download_apkm.py # Playwright-based APKMirror downloader (fallback)
patch.sh # Smali patching, signing, bundling
install.sh # ADB install helper (accepts .apkm, .xapk, or directory)
dump_device_props.sh # Dump Android TV device profile for rs-google-play
device_profiles/
nvidia_shield_tv.properties # NVIDIA Shield TV profile for Google Play downloads
Only needed if running scripts locally outside CI:
- Python 3.10+, Playwright (
pip install playwright && playwright install chromium) - Java, apktool, zipalign, apksigner
- ADB (for install.sh)
After installing (on an arm64 bundle, connected to an HDR-capable TV), start a session and watch the live decoder stats over ADB:
./scripts/stream_stats.sh 192.168.1.100:5555 # your TV's IP, or omit if on USBYou want to see, once the stream ramps up:
- Decoder Resolution:
3840x2160— full 4K. If it plateaus at2880x1620, the 2160p tier isn't being served (see the checklist below). - Video Codec: HEVC and a healthy bitrate (≈15–25 Mbps).
- On a genuinely HDR-capable device you'll also get HDR10/HLG output; on an HDR10-only panel (e.g. many NVIDIA Shield setups) the 4K plays as a clean SDR downconvert — full resolution, accurate colours, just not HDR's extra brightness/gamut. See Build options to push those setups toward true HDR10.
The patched build also exposes the in-app quality selector so you can confirm 3840×2160 is
offered (debug overlays are left off in release builds).
If you're stuck at 1620p, check in order:
- ABI —
adb shell getprop ro.product.cpu.abishould bearm64-v8a, and you must have installed theconfig.arm64_v8a.apksplit (notarmeabi_v7a). A 32-bit install can't do 4K. - Build flags — the bundle must be built with
F1TV_HLG_BYPASSandF1TV_DISPLAY_HDR_SPOOFon (both default; CI sets them). Public releases from before these defaults do not have them. - 4K TV — the panel must actually be 4K (the decode target follows the display). An HDR-capable panel additionally gets HDR output.
All patches are applied to every device (no runtime device gating) unless noted. The two that actually unlock 2160p are HDR advertise and display-capability spoof.
| Patch | File · method | What it does |
|---|---|---|
| UHD / device unlock | DeviceSupportImpl · validateIsUhdSupportedDevice, validateTmSdkSupport, validateLowRamDeviceSupport, validateApiLevelSupport |
Each returns Pair(true, null), so the device passes every UHD-capability gate (brand/product whitelist, secure-decoder probe, low-RAM check, API-level check). |
| Display-capability spoof (2160p unlock) | DeviceParameters · doesDisplaySupport |
Returns true so the core believes the panel accepts F1's HLG and serves the 2160p tiles instead of rejecting HLG upstream and dropping to 1620p SDR. Default on; disable with F1TV_DISPLAY_HDR_SPOOF=0. |
| HDR advertise (2160p unlock) | EGLRenderTarget · getIsBt2020HlgExtensionSupported |
Returns true, so DeviceParameters reports PQ+HLG EGL support and the backend offers the HDR 2160p tier. Default on; disable with F1TV_HLG_BYPASS=0. |
| Quality selector | DiagnosticsPreferenceManagerImpl · isVideoQualityEnabled |
Returns true so the in-app quality picker is visible. (The debug overlays are intentionally left off.) |
| 4K display size | TrueTVDisplaySizeHelper · getDefaultDisplaySize |
Hardcodes a 3840×2160 panel via getTrueDisplaySizeIfTV, lifting ClearVR's ~1.5× display-size cap (which otherwise limits a 1080p UI surface to 2880×1620). |
| PQ colour reroute (correct colours) | RenderTargetConfig · requireHLG, require2020PQ |
Routes F1's HLG content through the EGL PQ colorspace the device supports (requireHLG→false, require2020PQ→PQ‖HLG) so the 4K tiles are correctly gamut-converted instead of shown washed-out. Default on; disable with F1TV_PQ_REROUTE=0. |
| Render path | RenderAPIConfig · getNRPTextureBlitMode |
Default: EGL/GL path (patch skipped) so ClearVR composites and does a correct BT.2020→Rec.709 conversion. Set F1TV_DIRECT_TO_VIEW=1 to force NATIVE_ANDROID_DIRECT_TO_VIEW (decoder→SurfaceView) on weak/Amlogic GPUs that drop frames — at the cost of washed-out HDR colours. |
| Decoder capability spoof | DecoderCapability · getAsCoreProtobuf |
Reports non-zero secure tile slots/rows/cols (16/5/5) so the backend serves the full-resolution tile tier instead of a reduced one. |
| NVIDIA workaround off | Quirks · deviceNeedsNoPostProcessWorkaround |
Returns false so the decoder isn't forced into a lower-quality no-post-process path on NVIDIA devices. |
| Device-model spoof | TvApplication · getRequestHeader |
Sends model=chromecast in the x-f1-device-info header. |
| Version tag | apktool.yml, BuildConfig |
Appends -UHD to the version name so patched builds are identifiable. |
patch.sh reads these environment variables (all default to the values below; CI uses the defaults):
| Variable | Default | Effect |
|---|---|---|
F1TV_HLG_BYPASS |
1 |
Advertise EGL HDR (PQ+HLG) so the backend offers the 2160p tier. Required for 4K. |
F1TV_DISPLAY_HDR_SPOOF |
1 |
Force the display-capability check to accept all HDR types so the core serves 2160p on HDR10-only panels. Required for 4K on the Shield. |
F1TV_PQ_REROUTE |
1 |
Render the HDR tiles through the EGL PQ colorspace and gamut-convert them correctly (fixes washed-out colours). |
F1TV_DIRECT_TO_VIEW |
0 |
Default off = EGL/GL render path with correct 4K colours. Set 1 to force decoder→SurfaceView direct rendering on weak/Amlogic GPUs that drop frames on the GL path (trades correct HDR colours for smoothness). |
Result on an NVIDIA Shield + HDR10 TV: true 3840×2160 with accurate colours, output as a clean SDR downconvert (the Shield's GPU lacks the HLG EGL colorspace, so full HDR10 to the panel isn't reliable — but the 4K resolution and colour accuracy are the wins).
For personal/educational use only.