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

I am interested in a restore feature here. I've got the new setup in place but don't really want to re-enter all the stuff again.
Looking forward to the update.
Great work as always.

Thanks worked just fine

Updated to the Development version today, so far, so good. As many users have LinkPi, thought I would grab some Screen Shots of what the Steaming Endpoint error looks like.

LinkPi Default Steam URL Format: http://x.x.x.x/live/steam1

Working URL to LinkPi stream without the "live" redirect, formatted as https://x.x.x.x:8090/steam1

Thanks for all the hard work @turtletank .

Full backup and restore would be awesome! :slightly_smiling_face:

I just pushed a new development build (20250727-2). It only includes some small changes to the preview player window in the web interface. It's just a guess as I haven't yet been able to duplicate the issue that had been reported.

If you were running into tuners that wouldn't unlock after testing them with the preview feature, please give this new build a shot and let me know if it helps. Please make sure to refresh the page in your browser.

If these changes don't help, please try completely closing your browser window when all of the tuners are locked and see if that results in them getting unlocked. Thanks!

One of my Google Chromecast with Google TV updated to android 14 and I no longer have access to wired adb over the usb c ethernet adapter I'm using. Has anyone been able to work around this?

I have done some testing and sent reports and logs to you by message.

This evening, I have a question about the "keep device awake" feature. In testing, I have noticed what others have previously mentioned. YouTube TV channels tune rather quickly (5-6 sec). Peacock and Paramount+ are much slower. (15-20 sec) Would setting the keep awake function to one of the slower tuning app packages help speed tuning for that app?

Unfortunately I don't know of a fix for this. It's possible to use the newer wifi debugging feature to enable traditional networking debugging, but the whole thing resets each time the Chromecast is rebooted which is very annoying. I hated doing as such, but it was much easier to just replace it with a $20 Onn streamer from Walmart. :frowning:

This really depends. I think both of those apps need to be restarted before loading content via a deep link (compatibility mode). So if that's the case, making this change would offer no improvement. If the app will load deep link content if it is opened first, but hasn't been used yet, then a custom configuration could be created that closes the app after playback, but not before. This might offer an improvement.

I have done some testing with development using a ccwgtv android 12 device and YTTV, MAX, Peacock, and Paramount as sources. All of them are working and the tuner is releasing a few seconds after playback is stopped. The only issue I am seeing is in regards to the Olivetin "ADBTuner Device Alerter". It is now sending out device alerts every time it runs on the 30 minute schedule I have set. This is not happening on the current stable release.

Yeah, I had to stop the Olivetin Alerter. It used to only alert on rare occasions. Once or twice a week. Now, as you mention, it was sending alerts every time I had a scheduled check.

I tested this a bit yesterday evening, after posting my question. Tuning was not noticeably affected by enabling the keep awake feature.

@turtletank It sounds like the status page JSON is no longer reporting device_connected reliably in the development build.

It looks like the json output format has changed in development version. New categories of "version" and "proxy_routes"

Thanks for the heads-up. I didn't realize that anyone was parsing the json output of the script like you are doing. I added some new information to that output which broke your script.

Would you mind updating your script to just check the returned http status code since ADBTuner itself will return a 500 status if any of the devices or endpoints are not connected.

Something like this would probably work as a quick update to your script:

adbtunerStatus=$(curl -s -o /dev/null -w '%{http_code}' http://$adbtunerHostPort/up)
adbtunerNotConnected=$(echo "$adbtunerStatus" | grep -v "200")
2 Likes

Good idea. That looks better than the way I was doing it, and should work across builds of ADBTuner.

I installed the development 20250728-1 version yesterday. I tried to cause hangs and never succeeded. Overall it seems to tune faster than previous versions.

I pushed a small update to the development version (20250730-3) this morning. It includes fixes for two bugs that could have caused the app/container to crash and restart. If you are using the development version I recommend installing this update.

1 Like

I decided to go with a slight variation on your idea, which still triggers based on any status code other than 200 being returned, but also captures the entire JSON object to send via e-mail alert or Apprise:

    adbtunerStatus=$(curl -s -w '%{http_code}' http://$adbtunerHostPort/up)
    adbtunerNotConnected=$(echo "${adbtunerStatus: -3}" | grep -v "200")
    adbtunerStatus=${adbtunerStatus:0:${#adbtunerStatus}-3}

The OliveTin ADBTuner Device Alerter Action will now trigger whenever the status code returned from ADBTuner indicates there's issue with your connect devices, streaming endpoints, or when ADBTuner itself isn't responding. The alert e-mail, or notification sent via Apprise, will still include whatever data is sent back in the ADBTuner response.

This should allow the alerter to work across current and future builds of ADBTuner.

Pushed a few minutes ago as bnhf/olivetin:latest (aka bnhf/olivetin:2025.07.30)

Is this one of the crashes you mention above? It crashed and restarted during a recording last night...

2025-07-30 09:30:13.498 - maintenance - Skipping keep alive tasks as it's currently the top or middle of the hour.
fatal error: concurrent map writes
goroutine 34298 [running]:
internal/runtime/maps.fatal({0x77e394?, 0x724f20?})
	/usr/local/go/src/runtime/panic.go:1058 +0x18
main.trackConnection({0xc000098198, 0x16}, 0xc000114c90, 0xc000076af0)
	/app/proxy_server.go:116 +0x2a5
created by main.proxyHandler in goroutine 34231
	/app/proxy_server.go:192 +0x63a
goroutine 1 [chan receive, 994 minutes]:
main.main()
	/app/proxy_server.go:403 +0x55b
goroutine 7 [select]:
main.cleanupInactiveConnections(0xc000076150)
	/app/proxy_server.go:128 +0xb2
created by main.main in goroutine 1
	/app/proxy_server.go:393 +0x496
goroutine 8 [IO wait]:
internal/poll.runtime_pollWait(0x7fc3bdd54740, 0x72)
	/usr/local/go/src/runtime/netpoll.go:351 +0x85
internal/poll.(*pollDesc).wait(0xc00011e000?, 0x900000036?, 0x0)
	/usr/local/go/src/internal/poll/fd_poll_runtime.go:84 +0x27
internal/poll.(*pollDesc).waitRead(...)
	/usr/local/go/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Accept(0xc00011e000)
	/usr/local/go/src/internal/poll/fd_unix.go:620 +0x295
net.(*netFD).accept(0xc00011e000)
	/usr/local/go/src/net/fd_unix.go:172 +0x29
net.(*TCPListener).accept(0xc000134000)
	/usr/local/go/src/net/tcpsock_posix.go:159 +0x1b
net.(*TCPListener).Accept(0xc000134000)
	/usr/local/go/src/net/tcpsock.go:380 +0x30
net/http.(*Server).Serve(0xc0000cc000, {0x7fbc00, 0xc000134000})
	/usr/local/go/src/net/http/server.go:3424 +0x30c
net/http.(*Server).ListenAndServe(0xc0000cc000)
	/usr/local/go/src/net/http/server.go:3350 +0x71
main.main.func2()
	/app/proxy_server.go:396 +0x1b
created by main.main in goroutine 1
	/app/proxy_server.go:395 +0x4d6
goroutine 10 [syscall, 994 minutes]:
os/signal.signal_recv()
	/usr/local/go/src/runtime/sigqueue.go:152 +0x29
os/signal.loop()
	/usr/local/go/src/os/signal/signal_unix.go:23 +0x13
created by os/signal.Notify.func1.1 in goroutine 1
	/usr/local/go/src/os/signal/signal.go:152 +0x1f
goroutine 34306 [select, 112 minutes]:
net/http.(*persistConn).writeLoop(0xc0001c0240)
	/usr/local/go/src/net/http/transport.go:2590 +0xe7
created by net/http.(*Transport).dialConn in goroutine 34299
	/usr/local/go/src/net/http/transport.go:1945 +0x17a5
goroutine 34297 [IO wait, 112 minutes]:
internal/poll.runtime_pollWait(0x7fc3bdd543f8, 0x72)
	/usr/local/go/src/runtime/netpoll.go:351 +0x85
internal/poll.(*pollDesc).wait(0xc00019e000?, 0xc0004060a1?, 0x0)
	/usr/local/go/src/internal/poll/fd_poll_runtime.go:84 +0x27
internal/poll.(*pollDesc).waitRead(...)
	/usr/local/go/src/internal/poll/fd_poll_runtime.go:89
internal/poll.(*FD).Read(0xc00019e000, {0xc0004060a1, 0x1, 0x1})
	/usr/local/go/src/internal/poll/fd_unix.go:165 +0x27a
net.(*netFD).Read(0xc00019e000, {0xc0004060a1?, 0xc00003cfa0?, 0xc00003cf78?})
	/usr/local/go/src/net/fd_posix.go:55 +0x25
net.(*conn).Read(0xc000122000, {0xc0004060a1?, 0xc00011e200?, 0xc000136310?})
	/usr/local/go/src/net/net.go:194 +0x45
net/http.(*connReader).backgroundRead(0xc000406090)
	/usr/local/go/src/net/http/server.go:690 +0x37
created by net/http.(*connReader).startBackgroundRead in goroutine 34231
	/usr/local/go/src/net/http/server.go:686 +0xb6
goroutine 34231 [running]:
	goroutine running on other thread; stack unavailable
created by net/http.(*Server).Serve in goroutine 8
	/usr/local/go/src/net/http/server.go:3454 +0x485
goroutine 33905 [select, 112 minutes]:
net/http.(*persistConn).readLoop(0xc0001c0240)
	/usr/local/go/src/net/http/transport.go:2395 +0xc5f
created by net/http.(*Transport).dialConn in goroutine 34299
	/usr/local/go/src/net/http/transport.go:1944 +0x174f
2025-07-30 09:35:54.469 - tuner_management - Failed to get active connections. [Errno 111] Connection refused
Using database path: /app/.config/config.db
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
2025/07/30 09:36:09 Loaded routes from file:
  "/proxy/1" -> "http://192.168.1.161:8090/stream0"
  "/proxy/2" -> "http://192.168.1.161:8090/stream1"
  "/proxy/3" -> "http://192.168.1.161:8090/stream2"
  "/proxy/4" -> "http://192.168.1.161:8090/stream3"
  "/proxy/5" -> "http://192.168.1.161:8090/stream4"
INFO:     Started server process [10]
INFO:     Waiting for application startup.
2025/07/30 09:36:14 Received updated routes, but no changes detected.
INFO:     Application startup complete.
2025-07-30 09:36:14.499 - server - ADBTuner setup complete.
2025-07-30 09:36:14.499 - schema_verification - Verifying database schema (/app/.config/config.db).
2025-07-30 09:36:14.640 - server - Database schema verification passed successfully.
INFO:     Uvicorn running on http://0.0.0.0:5593 (Press CTRL+C to quit)
2025-07-30 09:36:15.072 - stream - [Tune QdCZ2Payropj8moXNXL4En] 192.168.1.100:5592 | Go-http-client/1.1