AndroidHDMI for Channels (ah4c): A virtual channel tuner using HDMI Encoder(s) + streaming stick(s)

@mackid1993 I don't think we should address this right at the moment, but it does appear some of the great stuff you've added assumes all connected streaming devices are adb-based. And, it also appears that adb connects are fired off even when the target is an Apple TV. This results in adb keys being created.

The nice thing is that we have a pretty simple gate with PYATV=true that we can apply to determine if a given function applies or not. However, from my perspective, I'd prefer to merge things sooner-rather-than later -- and then circle back to putting in some gates for things that don't apply to those with Apple TVs as streaming targets.

Let's take an example of PLAYBACK_DETECTION=true, as it stands it's 100% adb-oriented and will not work for atv users. However, it seems to me it could work, it's just not likely to be you or I that write the code.

So, when we do circle back, we can gate it for the time being -- and in the meantime someone may raise their hand to offer to see if it can be adapted using pyatv tools.

Yeah, I think the best option is to gate these features to PYATV = FALSE until someone implements them.

I think the Apple TV will be less popular because they are so expensive. And they hold their value.

Also, I'm not sure if you know, but Apple just increased the prices on Apple TVs, so the old ones are probably going to get more expensive on eBay.

They used to be around $50 for a used atv 4k gen 2 without a remote on ebay. last time i checked they were in the $75 - $90 dollar range. I picked up a brand new gen 3 just before apple raised prices for &130. Now they are around $200.

so far i haven’t seen any other side affects that hurt the ATV tuner, other than in the logs. honestly i haven’t paid much attention to the adb connects and this may have been going on for a very long time- pre merge.

edit: I just went back to appletv:test and this is definitely a post merge issue,

I'm currently trying to fix Clicker for @bnhf , but as soon as I'm done with that, I will take a look at trying to gate off some of these Apple TV issues.

Could you give me a list of things that should be disabled if you're using an Apple TV? I'll just go through and gate them behind the environment variable.

Here's what Claude Code gave me when I was looking at this earlier:

What's actually broken
Four places, all defined inside main.go, call the adb binary with no PYATV check at all:

Function	main.go	Trigger
adbControl	899, 958	/api/tuner/:index/control/:action, /api/tuners/control/:action (remote buttons)
audioBaseline β†’ adbAudio	429	Every network tune, when PLAYBACK_DETECTION=TRUE
waitForPlayback β†’ adbAudio	239	Same tune, polls during playback
Fixing this means: when PYATV=true, none of these paths may exec adb. The remote-control endpoints should return "unsupported for pyatv tuners" instead of shelling out, and the playback-detection gate should skip straight to whatever fallback already exists (the code already treats an unreachable adb target as "gate on motion alone" β€” main.go:2293 β€” so a PYATV deployment should hit that same fallback state without ever spawning adb).

The actual obstacle
The logic that needs to change lives entirely in main.go, and CLAUDE.md is explicit: don't edit that file, even when it's clearly the right place, even for a real bug β€” "fix the contention instead, or raise it and wait to be told." There's no other file to redirect this fix into the way rule 7 does for the captions/driver contention, because this code doesn't call out to another component β€” it is the component.

Two honest ways to reconcile that:

A. Relocate, don't edit. Move adbControl, adbAudio, audioBaseline, waitForPlayback, adbKeycodes, and the two constants into a new file (e.g. tunerremote.go), matching the project's existing pattern of "domain logic gets its own file, main.go just wires it up" (same rule already applied to captions/model files). Since Go doesn't care which file in a package defines a symbol, every call site in main.go stays byte-identical β€” no inline edits, no reformatting. The PYATV gate goes inside the relocated functions. Net effect: main.go loses ~150-200 lines it currently owns.

B. Edit in place. Add PYATV checks directly at the two API handlers and inside audioBaseline/waitForPlayback, leaving everything else where it is. Smallest possible diff, but it's a direct edit to the protected file.

Either way, the same downstream work applies once the gate is in:

Add a small pyatvEnabled() helper (strings.EqualFold(os.Getenv("PYATV"), "true")).
Confirm waitForPlayback's caller (main.go:239) tolerates a nil baseline / no-op gate cleanly β€” no goroutine left blocked on ready/gateDone if the adb path is skipped outright rather than failing into it.
Decide the API response for pyatv tuners hitting /control/:action β€” 400 with a clear reason, not a silent no-op.
Update README.md's pyatv section if it currently implies remote-control buttons work there.
Since main and ui-refactor must stay byte-identical apart from Dockerfile/build.yml, whatever's implemented gets committed to main and cherry-picked to ui-refactor, same as caption work.
Option A is the one I'd lean toward β€” it's consistent with how the rest of the codebase already separates concerns out of main.go, and it doesn't touch a single existing line of that file. But given CLAUDE.md's instruction to wait to be told rather than assume, I want your call before I write any code.

AskUserQuestion
OUT
Your questions have been answered: "How should the PYATV/adb fix be implemented, given main.go is off-limits by default?"="Just write up the issue, no code yet". You can now continue with these answers in mind.
Issue: ADB keys generated under /root/.android when PYATV=true
Symptom: Users running with PYATV=true (pyatv/atvremote-based tuners, no Android devices) see adb key files (adbkey, adbkey.pub) appear under /root/.android in the container.

Root cause: docker-start-beta.sh correctly branches on PYATV at startup β€” it calls atvConnections (which only shells out to atvremote) instead of adbConnections when PYATV=true. But that branching is startup-script-only. The Go binary itself has no PYATV awareness (grep -r PYATV *.go returns nothing), and four code paths in main.go call the adb binary unconditionally, independent of tuner type:

Function	Location	Trigger
adbControl	main.go:899	POST /api/tuner/:index/control/:action β€” remote-control buttons in the UI
adbControl	main.go:958	POST /api/tuners/control/:action β€” bulk remote control
audioBaseline β†’ adbAudio	main.go:429	Every network tune, when PLAYBACK_DETECTION=TRUE
waitForPlayback β†’ adbAudio	main.go:239	Polls during the same tune
The adb client host tool generates its keypair on first invocation, whether or not any device answers β€” it doesn't need a successful connection, just a process start. So on a PYATV deployment with PLAYBACK_DETECTION=TRUE, the very first tune runs adb connect <appletv-ip> (main.go:2247), which can never succeed against an Apple TV, but still spins up the local adb server and drops the key files before waitForPlayback gives up after 3 failures (main.go:2292) and falls back to motion-only gating. The same thing happens if a user presses a remote-control button against a pyatv tuner in the web UI.

What "fixed" looks like:

/api/tuner/:index/control/:action and /api/tuners/control/:action should reject (e.g. 400, "not supported for pyatv tuners") instead of calling adbControl when PYATV=true.
The playback-detection tune path should skip audioBaseline/waitForPlayback entirely under PYATV=true and go straight to the "gate on motion alone" fallback state that already exists for unreachable adb targets β€” without ever spawning adb.
Why this isn't a quick patch here: the logic that needs to change is defined entirely inside main.go, and CLAUDE.md says not to edit that file β€” its diff against upstream is kept purely additive, and ui-refactor carries it. That rule holds "even when main.go looks like the right place," which this is. Unlike the caption/driver contention case (rule 7 in CLAUDE.md), there's no separate component to redirect the fix into β€” adbControl, adbAudio, audioBaseline, and waitForPlayback are defined in main.go itself, not called into it from elsewhere.

Two real options when this gets picked up (left for whoever owns that decision, per "raise it and wait to be told"):

Relocate the four functions (+ adbKeycodes, adbTimeout, adbGiveUp) into a new file (e.g. tunerremote.go) and gate on PYATV there. Every call site in main.go stays byte-identical since Go doesn't care which file in the package defines a symbol β€” but main.go still loses ~150-200 lines it currently owns, so this is a real diff even though no existing line is touched.
Edit main.go directly with inline PYATV checks at the four sites β€” smallest footprint, but a direct edit to the protected file.
Either path also needs: a pyatvEnabled() helper, confirming waitForPlayback's caller doesn't leave a goroutine blocked on ready/gateDone when the gate is skipped outright, and β€” since main and ui-refactor must stay byte-identical apart from Dockerfile/build.yml β€” committing to main and cherry-picking to ui-refactor.

Not sure about the recommendations, but this should at least give you an idea where to focus.

Thanks for that. I told it not to edit main.go because it would do that on its own. I wanted a bit more control over that file because it's the core logic of the entire tool. I'll probably just go in there and tell it to ignore the rule temporarily and gate everything off so the non-relevant features don't affect ATV users?

From the sample yml that @bnhf provided as i look at the descriptions of some of the environment definitions, there are a few that clearly are adb specific.

Some look very interesting, and could have use later on written with PYATV in mind., but not having them doesnt break anything that i know of on my stack. Other PYATV users with different tuning scripts might run into other issues that i dont know of.

Other than me looking at the logs and the adb persistant directory i really havent noticed anything other than the keypad emulation not working in the ui tuner status page - which again is very minor for now.

Later with the same PYATV=true, someone can add the correct pyatv commands to match the remote up down left right, etc instead of using the adb commands

:sparkling_heart:
this will make multiple instances much easier. :+1:
edit : how do the playback detection and CC respond with multiple ah4c containers?

I did get the beta setup for a tuner last night.. my poor w11 n100 just couldn't keep up, it's still entirely possible I have more bios work to do in it. I had the least delay with parakeet 100m, 120sec. It's been recording news all day on that tuner, so I'll look thru some of them to see how it did when it was the only recording vs with other recordings concurrent.

@bnhf and @mackid1993 I just tried something out.

Ive been using my testbed with two Apple TVs running the merged code. I dont have enough linkPi ports to test three tuners at the same time, but I just swapped out one of the Apple TVs with my ONN box. Just changed the tuner2 ipaddress in the YML

I WAS SURPRISED HOW MUCH WORKS as is with both android and Apple TV tuners running at the same time...

I was able to play with wscrappy was definitely sending adb commands and PYATV commands from the same stack...

I was always planning to implement separate stacks for android vs ATV tuning, keeping the scripts completely decoupled in different stacks , but this was an interesting experiment.

Should I hold off on gating those features then?

Id like @bnf to chime in, but the simplest approach for my use in the real world is to decouple android vs apple into different ah4c containers.

So dont let my experiment derail more important things that you are doing. Its your choice either way, whatever keeps your sanity in check - thats more important

Unless @bnf or others can think of some huge advantage that im missing with keeping the android and ATV tuners in the same container?

The issue is what it would take to merge PYATV and ADB tuning into the same scripts and whether it is worth the effort.

For now id lean towards separate containers.

I haven't had a chance to actually look at this new functionality yet (all I saw was the forum post about it), and it looks like things are changing quickly day-to-day and I won't have the free time to keep up. But when things settle down, or if there is anything specific you would like an extra set of eyes on, I'd be happy to take a look!

It's not a problem to connect ADB and ATV devices to the same encoder, but the container needs to be dedicated to one type of device or the other. ah4c is written to assume it has a pool of tuners available that can all do the same job with the same set of commands.

If you had a 5-port encoder, you could have a mix of devices, but the encoder ports with ADB devices would need to be assigned to an ah4c container with PYATV set to false, and the encoder ports with ATV devices assigned to a container with PYATV set to true.

Thanks for the offer. I think the codebase will be less kinetic very soon, so we'll get back to you at that point. A fresh/extra set of eyes will be welcome.

(post deleted by author)

Im just simply amazed it JUST WORKED without conflicts that crashed the container.

It shouldn't. I mean, maybe that's a good idea, though.
What about allowing users choose what script they want to run per tuner. Or something of that sort. It's worth thinking through, obviously, but while we're refactoring everything, it might make sense instead of running multiple containers, allow people, especially with constrained systems, to want to run one container.

That would be brilliant. I have a ATV and a FireTV on the same LinkPi encoder. One container: less maintenance. I'd say go for it, but you guys are already putting on a lot of work into this.