ADBTuner: A "channel tuning" application for networked Google TV / Android TV devices

Ok. Thanks. Let me chew on this for a while and see what might work with ADBTuner.

1 Like

No problem. I appreciate you looking into it.

The docker logs should tell you what text was matched on that screen, but yes, it may be as easy as adding "watch" as a string there.

Thanks much for the pointer! I found this in the log:

2026-04-18 01:26:10.112 - lib.adb - [Tune jjmfdEHHg7yNrh3YwSe4ry] On screen text: 'prime —  hornets vs. magic  live apr17 2026 4:00pm pdt alt winner earns 8 seed  nba on prime play-in tournament: hornets at magic followed by warriors at suns at 10 pm et more  .  © included with prime  related need help?  live live  :  ‘in  i'

2026-04-18 01:26:10.112 - stream - [Tune jjmfdEHHg7yNrh3YwSe4ry] Text match result: Text to match: ["who's watching", 'watch live', 'new', 'guest']. Matches found: None.

Another recording today worked as the button said "watch live" and matched.

Hey @turtletank I just wanted to offer my assistance if I can be of any help tracking down that bug that I reported given that both ADBTuner and ah4c are both affected. Please let me know if you would like me to test anything or if I can help in any way. I would be very happy to test anything or provide any logging or anything you need, just let me know. It seems like since both tools are affected it's more to do with Channels DVR's interpretation of the stream rather than the actual tool which is why I injected the null packets. It's basically like a keep alive to keep Channels on the hook until video data starts flowing, basically. The key was it only flows null packets when video data isn't flowing at all. It's usually only a couple of seconds that this happens for. When video data is flowing, it completely skips that and just tunes warm and immediately fires a stream.

I first picked up on it because I was rebooting nightly as I was using the USB cam input on my devices and it's recommended to regularly reboot when using that because they freeze. At this point it matters a little bit less to me because I don't need to reboot anymore as I have eight native ports and four LinkPi units. A literal LinkPile. LOL I picked up an ENC5-V2 in addition to my three ENC1-V3s.

I think the key is keeping the stream from dying while retrying until the encoder responds with video data essentially but while still allowing for an eventual timeout to pass on to another tuner or encoder or source. I did three retries five seconds apart to match Channels' native 15 second timeout. That was my logic in my PR. I'm not sure how sound it is because I am a a sysadmin not a real developer lol I just try to contribute back whenever I can however, and I've learned a lot in the last year or so.

I would prefer to use ADBTuner because with having eight tuners it's so much easier to manage that number of units in ADB Tuner's UI compared to Docker Compose.

Thank you for building this tool!! Please, please also do a Buy Me A Coffee page so I can donate to you. I would really like to contribute to your work. :slightly_smiling_face:

@mackid1993

I spent a decent amount of time looking into padding broken MPEG-TS streams with null data, and while I think it's a clever solution to your specific problem, I don't think I'm going to be adding support for this to ADBTuner at this time.

The primary reason for not doing as such is that I don't believe that it's necessarily an improvement for all users.

When video players encounter a null packet they tend to freeze on the last frame they were able to decode. If a frame hasn’t been decoded yet the player just kind of hangs there. I think this might be the ok if it was just for a few seconds, but not for 15-30 seconds. I believe it is better to close the stream and let the user/software decide if it wants to retry.

As I previously mentioned, the LinkPi reboot situation is also a mess because the endpoint comes up immediately after a reboot, but always drops out again once as the device seems to reset itself in some way. This can result in like 45 seconds of frozen video during the first 1.5 minutes after a reboot and during this time ADBTuner would never consider using another tuner for the same channel because the current tuner won't be released.

I did add a new feature to the development build (20260420-4) this morning that might help a bit. Each tuner now has a "Check Connectivity Before Using This Tuner" option associated with it. If enabled, ADBTuner will not assign this tuner for any new tuning requests if it cannot connect to the device (ADB) or the streaming endpoint.

If the connectivity check fails for a tuner, another tuner will be tried. ADBTuner tries to allocate a tuner 10 times so this provides a decent window for the endpoint to come back online.

Since you have multiple encoders, perhaps you can order them as such so it alternates between devices while trying to allocate a tuner? Tuner 1 could be on the ENC5, Tuner 2 on the ENC1, etc. This won't necessarily help if the streaming endpoint drops out while watching, but it will help with initial tuning operations or recovering from dropouts. Just don't reboot them all at the same time!

Note: This new option is disabled by default because it makes tuning slower. We aren't checking connectivity on all tuners up front, but even checking it on the first selected tuner adds a few seconds to every tuning operation.

Outside of this, it probably wouldn't be a huge lift to use your code as part of a dedicated proxy server that runs in a container. This mpeg patching proxy could sit between your LinkPi and all the apps that stream data from it. If I can help in any way with something like that let me know.

Thanks. I'm working on an external tool for ah4c (maybe can be adapted to ADBTuner) to inject the null packets. I'm writing it in rust to keep the footprint low, just running into some timing issues. I'm still testing it and trying to tweak things but it wouldn't involve any upstream changes, just the ability to run an executable which ah4c handles with the CMD environment variable. GitHub - mackid1993/ah4c-stream-rs · GitHub

So I ended up implementing a sidecar proxy inside my own fork of ah4c. I wonder if this can somehow be used with ADB Tuner. I'm just curious if there's a way for it to kind of hook into ADB Tuner somehow. The code's basically written and it works really well. I do have a PR open for ah4c to basically get this logic merged into their main branch. But I thought this might still be useful as a solution for ADBTuner if it's possible to maybe build as like a separate Docker container, which is doable on my end. I just don't know how it would sit in between.

Right now, it's built into my own ah4c container that is pulling in the source from a Git submodule. It all gets compiled into one Docker. If my PR merged I will most likely abandon this since there won't be any reason to maintain this and native code is obviously more robust than a proxy, but I figured I'd mention that I built this just in case someone can find some use for it. I stress tested the heck out of it tonight and couldn't get any weird behavior. It just worked. For a while it was acting weird, but I was able to pretty much stabilize it as far as I can tell.

1 Like

Is this ok to deploy with Portainer? It worked, but I want to make sure it doesn't create issues in the future:

services:
  adbtuner:
    image: turtletank99/adbtuner:latest
    container_name: adbtuner
    dns_search: localdomain
    init: true
    ports:
      - "5592:5592"  # Map host port to container port
    volumes:
      - adbtuner_config:/app/.config  # Persistent storage for configuration
    restart: unless-stopped

volumes:
  adbtuner_config:

You should use either turtletank99/adbtuner:stable or turtletank99/adbtuner:development

Looks good to me but I recommend using the development tag

Thank you. I tried to just change it and redeploy, but it didn't like it. Deleted and built a new one and it seems fine now. All settings were persistent.

Why is that your recommendation? I went with stable. Aways worry about something not working as expected.

Theres more bug fixes and more features in the development version. Thats the version I use and I haven't had any issues with it

@mackid1993

I added some extremely experimental code to the development build of ADBTuner (20260421-11) to support the generation of null mpeg-ts packets. It's disabled by default, you will need to add MPEG_NULL_PACKETS=True to your environment to enable it.

It uses null packets in two places:

  1. During tuning operations, null packets are sent instead of delaying the HTTP response until tuning is complete. This assumes that your configuration for the selected content allows for that (wait_for_video_playback_detection, use_fixed_delay, etc). This is interesting because it might provide the ability to hide the tuning process for longer than 30 seconds with no timeout.

  2. When streaming endpoint connections drop out during playback. This will allow the tuning session to stay active while your LinkPi(s) reboot. The video will freeze, and it's hard limited to 60 seconds before it drops. Note: in further testing, the ENC5 stops streaming twice after a reboot. The device isn't really stable until about 2 minutes after a reboot.

Known Issues:

  1. The video player in the web interface doesn't always respond well to this and will hang indefinitely. It seems to work fine in Channels DVR (Apple TV, MacOS) and in VLC.
  2. I had to extend the active session timeout from 3 seconds to 8 seconds to allow for more breathing room with this reconnect logic. This means a tuner will not be released until 8 seconds after all streaming has stopped. You can test other values by setting KNOWN_STREAM_DEFAULT_TIMEOUT in your environment.
  3. Sometimes after a switch, the stream doesn't start with a keyframe. This causes a quick green flash. If the encoder is inserting a GOP every 1-2 seconds (2 seconds is usually the recommended configuration) this will be rare.
  4. After resuming playback after a disconnect, the Channels app ends up "behind the timeline," and it won't let you fast forward back to live.

I'm still not sure I'm going to roll with this in the long term, but it's definitely worth testing and maybe providing as an option. Let me know how it works out for you.

I was not aware that null packets were an option for padding transport streams so thanks for finding and sharing that information.

2 Likes

THANK YOU SO MUCH!!!

From my quick test at work it's realy working well, I'll test more tonight!!! I was able to fix the whole behind the timeline thing in my vibecoded sidecar implementation I wrote for ah4c but you just saved me the trouble of forking, I also haven't run into that yet so maybe it's transient. I love both tools for different uses but ADBTuner really excels at managing a large number of tuners so this is a huge thing for me. Thank you so much for taking the time out of your day to add it even behind a hidden flag!

I really really appreciate it and if you ever open up donations I'll be the first to buy you a coffee! Being able to reliably tune my Ospreys in 3-4 seconds while sleeping and waking is such a win! I can't thank you enough!

I'm having an intermittent issue. I record a show from Comedy Central using ADBTuner, this has worked well since I first set it up, which was when I first used docker. I know just enought about docker now to have notes on how to restart ADBTuner when I reboot the linux server for any reason. I have 4 onn 4K android boxes connected to a LinkPi encoder.

Starting a couple of months ago, occasional episodes would play back with no sound. I looked at the media file, and it has an audio track, just looks like a bit of noise down at -50 db or something. This might happen once in two weeks, maybe twice. I've updated and reset all the android tv pucks, but it happened again last Thursday. Is there a way I can check and see which tuner was being used and maybe take it out of rotation, see if the issue goes away?

(I think I've seen it on one or two other recordings, but they all were through ADBTuner, not TVE. ADBTuner status page looks all green.)

How does the keep alive option work with ADBtuner? I have this selected package name, com.hulu.livingroomplus, in the package field. I see there is a checkbox to seemingly enable keep alive, but adbtuner won't let me select it. I have a Tivo Stream 4K that works most of the time with ADBtuner. Trying to increase reliability. It seems to fail when trying to record at like 3am.

@turtletank I just spent some time tuning around with my Sofabaton U2 so I could key in channel numbers really quickly. And I perceived no issues at all whatsoever. It worked perfectly from a cold start.

I looked in the logs and each tune was about 5.8 seconds which I think is fantastic, thank you so much for implementing this it's so far working really well and I'll let you know if I notice anything weird as I watch TV later on tonight.

Click "Edit", then check the "Keep Device Awake" box, then click the "Save Changes" button.

Issue Description:
Utilizing FDL /ADBTuner to record college games on ESPN+. When I only record 1 game at a time, the recording completes as expected. When I attempt to record 2 games at a time, the 1st will record the entire game but the 2nd game will switch from game to FireTV Home Screen at some point. It then eventually stops recording.

Both sticks are 4K Max 2nd Generationon a Link Pi Enc5V2. ADBTuner is on the latest tag. All was setup utilizing Project One-Click.

I've changed the tuner priority with no affect on the outcome.

What I noticed on the recording of game 2 was the switch from game to Home Screen occurs at the end of the Game 1 recording ( or in that vicinity).

I attached the ADBTuner log snippet from Portainer for review.

Is there a way to solve this or is there another log to show what's occurring? I’d really like to record 2 games completely simultaneously.

--------------------------------------------------
2026-04-22 01:34:50.193 - server - 
--------------------------------------------------
Tuner "Encoder 4" is currently in use and locked.
Tune ID: GhCsJPRw5MbWVRjGyHR2hc
Channel: ESPN 227
Lock Obtained: 2026-04-21 23:00:00 (154.84 minutes ago)
Last Seen: 2026-04-22 01:34:49 (1.19 seconds ago)
Bytes Transferred: 4596973180 (4384.02MB)
Remote User Agent:
channels-dvr/2026.04.12.0247 Go-http-client/1.1 
(linux-x86_64)
--------------------------------------------------
--------------------------------------------------
Tuner "Encoder 5" is currently in use and locked.
Tune ID: Ti79t3BBFcjnfoaWDzi3d5
Channel: ESPN 246
Lock Obtained: 2026-04-21 23:30:00 (124.84 minutes ago)
Last Seen: 2026-04-22 01:34:49 (1.19 seconds ago)
Bytes Transferred: 3793739796 (3617.99MB)
Remote User Agent:
channels-dvr/2026.04.12.0247 Go-http-client/1.1 
(linux-x86_64)
--------------------------------------------------
2026-04-22 01:35:02.861 - tuner_management - Releasing tuner 5 as it has been inactive for 3 seconds.
2026-04-22 01:35:02.863 - tuner_management - Releasing tuner: {'lock_obtained': 1776812400, 'tuner_name': 'Encoder 4', 'name': 'ESPN 227', 'number': 226, 'provider_name': 'sportscenter', 'tuner_id': 5, 'channel_id': 227, 'tuning_status': 'tuned', 'request_key': 'GhCsJPRw5MbWVRjGyHR2hc', 'target_package': 'com.espn.gtv'}
2026-04-22 01:35:03.665 - tuner_management - [Tune GhCsJPRw5MbWVRjGyHR2hc] Using configuration: FruitDeepLinks - ESPN+ v2.0 (FireTV and AndroidTV) (51af5028-092f-4ddc-b4ea-d5e5fca58cac) for channel cleanup.
2026-04-22 01:35:03.665 - lib.adb - [Tune GhCsJPRw5MbWVRjGyHR2hc] ADB: 10.0.1.54 - input keyevent KEYCODE_MEDIA_STOP
2026-04-22 01:35:05.340 - lib.adb - [Tune GhCsJPRw5MbWVRjGyHR2hc] ADB: 10.0.1.54 - input keyevent KEYCODE_MEDIA_PAUSE
2026-04-22 01:35:06.412 - lib.adb - [Tune GhCsJPRw5MbWVRjGyHR2hc] ADB: 10.0.1.54 - input keyevent KEYCODE_HOME
2026-04-22 01:35:07.384 - tuner_management - [Tune GhCsJPRw5MbWVRjGyHR2hc] Released tuner (5).
2026-04-22 01:35:07.385 - tuner_management - [Tune GhCsJPRw5MbWVRjGyHR2hc] Cleanup complete. Encoder 4 (ESPN 227)
2026-04-22 01:35:07.385 - tuner_management - Releasing tuner 6 as it has been inactive for 6 seconds.
2026-04-22 01:35:07.385 - tuner_management - Releasing tuner: {'lock_obtained': 1776814200, 'tuner_name': 'Encoder 5', 'name': 'ESPN 246', 'number': 245, 'provider_name': 'sportscenter', 'tuner_id': 6, 'channel_id': 246, 'tuning_status': 'tuned', 'request_key': 'Ti79t3BBFcjnfoaWDzi3d5', 'target_package': 'com.espn.gtv'}
2026-04-22 01:35:07.550 - tuner_management - [Tune Ti79t3BBFcjnfoaWDzi3d5] Using configuration: FruitDeepLinks - ESPN+ v2.0 (FireTV and AndroidTV) (51af5028-092f-4ddc-b4ea-d5e5fca58cac) for channel cleanup.
2026-04-22 01:35:07.551 - lib.adb - [Tune Ti79t3BBFcjnfoaWDzi3d5] ADB: 10.0.1.51 - input keyevent KEYCODE_MEDIA_STOP
2026-04-22 01:35:08.902 - lib.adb - [Tune Ti79t3BBFcjnfoaWDzi3d5] ADB: 10.0.1.51 - input keyevent KEYCODE_MEDIA_PAUSE
2026-04-22 01:35:09.872 - lib.adb - [Tune Ti79t3BBFcjnfoaWDzi3d5] ADB: 10.0.1.51 - input keyevent KEYCODE_HOME
2026-04-22 01:35:10.871 - tuner_management - [Tune Ti79t3BBFcjnfoaWDzi3d5] Released tuner (6).
2026-04-22 01:35:10.872 - tuner_management - [Tune Ti79t3BBFcjnfoaWDzi3d5] Cleanup complete. Encoder 5 (ESPN 246)
2026-04-22 01:45:59.823 - uvicorn.access - 172.18.0.1:0 - "GET / HTTP/1.1" 200
2026-04-22 01:51:28.449 - uvicorn.access - 172.18.0.1:0 - "GET / HTTP/1.1" 200
2026-04-22 01:59:30.892 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 02:09:30.870 - tuner_management - Caching installed apps list for all tuners
2026-04-22 02:29:31.912 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 02:59:31.922 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 03:09:31.906 - tuner_management - Caching installed apps list for all tuners
2026-04-22 03:29:32.911 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 03:59:32.939 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 04:09:32.899 - tuner_management - Caching installed apps list for all tuners
2026-04-22 04:29:33.471 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 04:59:33.482 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 05:09:33.463 - tuner_management - Caching installed apps list for all tuners
2026-04-22 05:29:34.042 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 05:59:34.052 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 06:09:34.033 - tuner_management - Caching installed apps list for all tuners
2026-04-22 06:29:34.559 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 06:59:34.570 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 07:09:34.550 - tuner_management - Caching installed apps list for all tuners
2026-04-22 07:29:35.097 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 07:59:35.108 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 08:09:35.089 - tuner_management - Caching installed apps list for all tuners
2026-04-22 08:29:35.637 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 08:59:35.651 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 09:09:35.626 - tuner_management - Caching installed apps list for all tuners
2026-04-22 09:29:36.162 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 09:59:36.175 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 10:09:36.154 - tuner_management - Caching installed apps list for all tuners
2026-04-22 10:29:36.730 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 10:59:36.742 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 11:09:36.721 - tuner_management - Caching installed apps list for all tuners
2026-04-22 11:29:37.327 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 11:59:37.341 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 12:09:37.320 - tuner_management - Caching installed apps list for all tuners
2026-04-22 12:29:37.913 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 12:59:37.925 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 13:09:37.902 - tuner_management - Caching installed apps list for all tuners
2026-04-22 13:29:38.442 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 13:59:38.456 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 14:03:00.299 - uvicorn.access - 172.18.0.1:0 - "GET /channels.m3u8?provider=sportscenter HTTP/1.1" 200
2026-04-22 14:09:38.433 - tuner_management - Caching installed apps list for all tuners
2026-04-22 14:29:38.979 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 14:59:38.992 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
2026-04-22 15:09:38.971 - tuner_management - Caching installed apps list for all tuners

Try the development tagged version of ADBTuner.

Also, adding this environment variable may help:

KNOWN_STREAM_DEFAULT_TIMEOUT=10

EDIT: Fixed incorrect env var name.