Fix/android blackscreen and pad mapping #371

Closed
FlipperDan wants to merge 2 commits from FlipperDan/punktfunk:fix/android-blackscreen-and-pad-mapping into main
Member

What & Why

1. Black screen on initial stream probe (crates/punktfunk-core)

  • What: Limits the startup probe burst bandwidth to 150 Mbps on Android (TARGET_PROBE_BANDWIDTH_BPS), and immediately requests an IDR keyframe if the startup probe fails to complete.
  • Why: Under high bitrate settings or lossy Wi-Fi links on Fire TV / Android TV, unthrottled probe packet bursts cause bufferbloat and packet loss at stream startup. If the first keyframe is dropped during the probe, the client remains stuck on a black screen. Limiting probe bandwidth prevents bufferbloat, and requesting an immediate keyframe ensures the stream recovers cleanly without hanging.

2. DualSense Bluetooth button mapping and input source routing (clients/android)

  • What:
    • Treats DualSense controllers reporting named triggers (AXIS_BRAKE/AXIS_GAS) as PadButtons.NATIVE, preserving standard face buttons (✕/○/□/△).
    • Explicitly maps DualSense Touchpad Click (0x13d / 317) to BTN_TOUCHPAD and Mute button (0x13e / 318) to BTN_MISC1 (triggering client microphone toggle).
    • Expands MainActivity gamepad event checks (isFromSource(SOURCE_GAMEPAD) || isFromSource(SOURCE_JOYSTICK) || Gamepad.isPad(dev)) so Bluetooth gamepad buttons delivered under SOURCE_KEYBOARD or SOURCE_JOYSTICK by Fire OS are not dropped.
    • Adds explicit guard in Gamepad.isSonyPad to prevent Xbox controllers from ever matching Sony layouts.
  • Why: Fire OS delivers standard face button keycodes for DualSense over Bluetooth, but tags certain buttons (like Triangle, Touchpad, and Mode) with SOURCE_KEYBOARD. The previous checks dropped these events and erroneously applied straight-through shift heuristics.

User-facing fact changed?
n/a (Bug fixes in Android client streaming probe and Bluetooth gamepad input routing; no docs, CLI flags, ports, or config schemas changed).

### What & Why #### 1. Black screen on initial stream probe (`crates/punktfunk-core`) - **What**: Limits the startup probe burst bandwidth to 150 Mbps on Android (`TARGET_PROBE_BANDWIDTH_BPS`), and immediately requests an IDR keyframe if the startup probe fails to complete. - **Why**: Under high bitrate settings or lossy Wi-Fi links on Fire TV / Android TV, unthrottled probe packet bursts cause bufferbloat and packet loss at stream startup. If the first keyframe is dropped during the probe, the client remains stuck on a black screen. Limiting probe bandwidth prevents bufferbloat, and requesting an immediate keyframe ensures the stream recovers cleanly without hanging. #### 2. DualSense Bluetooth button mapping and input source routing (`clients/android`) - **What**: - Treats DualSense controllers reporting named triggers (`AXIS_BRAKE`/`AXIS_GAS`) as `PadButtons.NATIVE`, preserving standard face buttons (✕/○/□/△). - Explicitly maps DualSense Touchpad Click (`0x13d` / 317) to `BTN_TOUCHPAD` and Mute button (`0x13e` / 318) to `BTN_MISC1` (triggering client microphone toggle). - Expands `MainActivity` gamepad event checks (`isFromSource(SOURCE_GAMEPAD) || isFromSource(SOURCE_JOYSTICK) || Gamepad.isPad(dev)`) so Bluetooth gamepad buttons delivered under `SOURCE_KEYBOARD` or `SOURCE_JOYSTICK` by Fire OS are not dropped. - Adds explicit guard in `Gamepad.isSonyPad` to prevent Xbox controllers from ever matching Sony layouts. - **Why**: Fire OS delivers standard face button keycodes for DualSense over Bluetooth, but tags certain buttons (like Triangle, Touchpad, and Mode) with `SOURCE_KEYBOARD`. The previous checks dropped these events and erroneously applied straight-through shift heuristics. --- **User-facing fact changed?** n/a (Bug fixes in Android client streaming probe and Bluetooth gamepad input routing; no docs, CLI flags, ports, or config schemas changed).
FlipperDan added 2 commits 2026-08-22 13:50:23 +00:00
fix(clients/android): improve DualSense Bluetooth button mapping and input source routing
ci / web (pull_request) Canceled after 0s
ci / docs-site (pull_request) Canceled after 0s
ci / bun-nix (pull_request) Canceled after 0s
ci / docs-drift (pull_request) Canceled after 0s
windows-client / client (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (pull_request) Canceled after 0s
windows-client / client (x64, , x86_64-pc-windows-msvc, C:\t) (pull_request) Canceled after 0s
android / android (pull_request) Canceled after 0s
apple / swift (pull_request) Canceled after 0s
apple / distribute (pull_request) Canceled after 0s
apple / screenshots (pull_request) Canceled after 0s
ci / rust (pull_request) Canceled after 0s
ci / rust-arm64 (pull_request) Canceled after 0s
a3509eaa5e
Owner

Thanks @FlipperDan — the diagnosis here is good, and all three problems are real ones that nobody had spotted. They're now fixed on main as #379 (the probe) and #380 (the pad mapping). What changed is where each fix lands, not whether it was worth making:

The probe. The 2 Gbps target really is too high, and your read of why is right. Rather than a cfg!(target_os = "android") default, #379 derives it from stream_cap_kbps × 2 (capped at the old 2 Gbps). The ABR already clamps any measured ceiling to that cap, so everything the burst measures above cap / 0.7 was being discarded anyway — the height was pure bufferbloat. Deriving it fixes webOS and every other constrained client at once, and it can't cap the wired Shield / Wi-Fi 6E clients that genuinely have the capacity (a flat 200 Mbps would have, on exactly the high modes).

The keyframe on probe end. Kept essentially as you wrote it — right place, right emitter, funnelled through the coalescer. The guard is widened to compare against frames_completed snapshotted at the burst's leading edge instead of against 0, so it also covers a mid-session "Test connection" that kills a running stream.

The pad mapping. The one to flag for next time: 0x13d/0x13e are BTN_THUMBL/BTN_THUMBR — L3 and R3 — in the standard Linux mapping. They only mean touchpad and mute inside the straight-through report order a driverless pad enumerates in, which is what PadButtons.GENERIC_SONY is. Resolving them above padMap(dev) (and adding them to SONY_MODERN) costs both stick clicks on every Xbox pad, Switch Pro, 8BitDo, Steam Deck and hid-playstation DualSense, and puts the mic toggle on R3. #380 puts the two rows inside GENERIC_SONY and nowhere else.

Same idea behind the source-routing fix: widening isPad on vendorId catches those vendors' keyboards and mice too (0x045E is Microsoft's VID for Sculpt keyboards and Arc mice), which routes a keyboard's arrow keys into the D-pad and takes away a remote's Back as the way out of a stream. #380 widens to the device but only for KeyEvent.isGamepadButton keycodes, which is exactly the set that can't be anything else.

Two more worth knowing: correct()'s genericKeyCode guard is what keeps the tables from touching a pad that already works, so it needs to stay; and BTN_MISC1 is the wire's misc/QAM bit — Sc2Device puts a Steam Controller 2's QAM button on it — so the mic toggle is gated on the pad actually having a mute button, or QAM would mute people's mics.

Closing this in favour of the two merged PRs.

Thanks @FlipperDan — the diagnosis here is good, and all three problems are real ones that nobody had spotted. They're now fixed on `main` as #379 (the probe) and #380 (the pad mapping). What changed is *where* each fix lands, not whether it was worth making: **The probe.** The 2 Gbps target really is too high, and your read of why is right. Rather than a `cfg!(target_os = "android")` default, #379 derives it from `stream_cap_kbps × 2` (capped at the old 2 Gbps). The ABR already clamps any measured ceiling to that cap, so everything the burst measures above `cap / 0.7` was being discarded anyway — the height was pure bufferbloat. Deriving it fixes webOS and every other constrained client at once, and it can't cap the wired Shield / Wi-Fi 6E clients that genuinely have the capacity (a flat 200 Mbps would have, on exactly the high modes). **The keyframe on probe end.** Kept essentially as you wrote it — right place, right emitter, funnelled through the coalescer. The guard is widened to compare against `frames_completed` snapshotted at the burst's leading edge instead of against 0, so it also covers a mid-session "Test connection" that kills a running stream. **The pad mapping.** The one to flag for next time: `0x13d`/`0x13e` are `BTN_THUMBL`/`BTN_THUMBR` — L3 and R3 — in the standard Linux mapping. They only mean touchpad and mute inside the straight-through report order a driverless pad enumerates in, which is what `PadButtons.GENERIC_SONY` is. Resolving them above `padMap(dev)` (and adding them to `SONY_MODERN`) costs both stick clicks on every Xbox pad, Switch Pro, 8BitDo, Steam Deck and `hid-playstation` DualSense, and puts the mic toggle on R3. #380 puts the two rows inside `GENERIC_SONY` and nowhere else. Same idea behind the source-routing fix: widening `isPad` on `vendorId` catches those vendors' keyboards and mice too (`0x045E` is Microsoft's VID for Sculpt keyboards and Arc mice), which routes a keyboard's arrow keys into the D-pad and takes away a remote's Back as the way out of a stream. #380 widens to the device but only for `KeyEvent.isGamepadButton` keycodes, which is exactly the set that can't be anything else. Two more worth knowing: `correct()`'s `genericKeyCode` guard is what keeps the tables from touching a pad that already works, so it needs to stay; and `BTN_MISC1` is the wire's misc/**QAM** bit — `Sc2Device` puts a Steam Controller 2's QAM button on it — so the mic toggle is gated on the pad actually having a mute button, or QAM would mute people's mics. Closing this in favour of the two merged PRs.
enricobuehler closed this pull request 2026-08-23 07:39:50 +00:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#371