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

    raise exc
  File "/usr/local/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/usr/local/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
    raise e
  File "/usr/local/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.11/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.11/site-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.11/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/fastapi/routing.py", line 241, in app
    raw_response = await run_endpoint_function(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/fastapi/routing.py", line 169, in run_endpoint_function
    return await run_in_threadpool(dependant.call, **values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/starlette/concurrency.py", line 41, in run_in_threadpool
    return await anyio.to_thread.run_sync(func, *args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/anyio/to_thread.py", line 56, in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2177, in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 859, in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/server.py", line 586, in stream_redirect
    available_tuner = tuner_manager.reserve_tuner()
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/server.py", line 99, in reserve_tuner
    selected_tuner = available_tuners[0]
                     ~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range
2024-08-24 17:24:53.154 - server - Available Tuners:
2024-08-24 17:24:53.154 - server - Using first available tuner (by priority).
2024-08-24 17:24:53.155 - uvicorn.access - 172.23.0.1:40064 - "GET /stream/1 HTTP/1.1" 500
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/usr/local/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 435, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/fastapi/applications.py", line 284, in __call__
    await super().__call__(scope, receive, send)
  File "/usr/local/lib/python3.11/site-packages/starlette/applications.py", line 122, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/usr/local/lib/python3.11/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/usr/local/lib/python3.11/site-packages/starlette/middleware/sessions.py", line 86, in __call__
    await self.app(scope, receive, send_wrapper)
  File "/usr/local/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/usr/local/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/usr/local/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
    raise e
  File "/usr/local/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.11/site-packages/starlette/routing.py", line 718, in __call__
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.11/site-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.11/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
               ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/fastapi/routing.py", line 241, in app
    raw_response = await run_endpoint_function(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/fastapi/routing.py", line 169, in run_endpoint_function
    return await run_in_threadpool(dependant.call, **values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/starlette/concurrency.py", line 41, in run_in_threadpool
    return await anyio.to_thread.run_sync(func, *args)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/anyio/to_thread.py", line 56, in run_sync
    return await get_async_backend().run_sync_in_worker_thread(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 2177, in run_sync_in_worker_thread
    return await future
           ^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 859, in run
    result = context.run(func, *args)
             ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/server.py", line 586, in stream_redirect
    available_tuner = tuner_manager.reserve_tuner()
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/app/server.py", line 99, in reserve_tuner
    selected_tuner = available_tuners[0]
                     ~~~~~~~~~~~~~~~~^^^
IndexError: list index out of range

Thanks. A new build has been pushed with a fix for this. I failed to check if any tuners were actually available before assigning one. Oops.

No tuners available ..... I had 4 available. They disappeared ... re-adding them

2024-08-24 21:36:07.290 - server - No tuners available.
2024-08-24 21:36:07.293 - uvicorn.access - 172.23.0.1:36382 - "GET /stream/1 HTTP/1.1" 404
2024-08-24 21:36:18.444 - server - No tuners available.
2024-08-24 21:36:18.448 - uvicorn.access - 172.23.0.1:38164 - "GET /stream/1 HTTP/1.1" 404
2024-08-24 21:36:29.324 - server - No tuners available.
2024-08-24 21:36:29.326 - uvicorn.access - 172.23.0.1:56262 - "GET /stream/1 HTTP/1.1" 404
2024-08-24 21:37:06.068 - server - No tuners available.
2024-08-24 21:37:06.071 - uvicorn.access - 172.23.0.1:58410 - "GET /stream/4 HTTP/1.1" 404
2024-08-24 21:37:17.187 - server - No tuners available.
2024-08-24 21:37:17.190 - uvicorn.access - 172.23.0.1:35900 - "GET /stream/4 HTTP/1.1" 404
2024-08-24 21:37:28.098 - server - No tuners available.
2024-08-24 21:37:28.100 - uvicorn.access - 172.23.0.1:59744 - "GET /stream/4 HTTP/1.1" 404
2024-08-24 21:38:35.466 - server - No tuners available.
2024-08-24 21:38:35.467 - uvicorn.access - 172.23.0.1:35264 - "GET /stream/5 HTTP/1.1" 404
2024-08-24 21:38:46.613 - server - No tuners available.
2024-08-24 21:38:46.614 - uvicorn.access - 172.23.0.1:56904 - "GET /stream/5 HTTP/1.1" 404
2024-08-24 21:38:58.116 - server - No tuners available.
2024-08-24 21:38:58.117 - uvicorn.access - 172.23.0.1:48170 - "GET /stream/5 HTTP/1.1" 404

@turtletank ... Appears to be working after I re-added my tuners have no clue why they dissappeared.

Hmm.. Yeah the root cause for both of these errors was that there were no available tuners. It's really odd that they all disappeared like that. Please let me know if it happens again and maybe we can get some logs that might indicate how they got deleted. Thanks!

I'm considering picking up an HDMI encoder to pull in channels via YouTube TV but also the NESN 360 app. It's obviously a big investment to purchase the encoder and the Android TV devices all for a few channels.

I guess my question is, will this solution ALWAYS work or could Google change things on their end that would prevent this from not working in the future? Nearly $500 wanted if Google makes a switch.

Also, what's the most economical encoder devices for 2-3-4 inputs? I'm thinking I really just need a stream for NESN, NBC Sports, but don't want to purchase an encoder and then realize I need more later on.

Another question, are devices shareable? Meaning NESN could be watched on two devices at once. Can that stream from a single device be shared?

Your going to find it hard to beat the price per tuner on LinkPi 5 port tuner...You can find lots of discussion on here about them. Lots of people including me really like them but others have had some issues with black levels and such if I remember right.
Google CAN ALWAYS change things up and complicate our lives. I believe ADBTuner specifically relies exclusively on DeepLInks and ADB for controlling android boxes to "tune" channels. There are other options like AH4C that can use deeplinks,scripts or even pyremote to control the "tuners". ADBTuner is a good starting point and will handle 90% of use cases. I have not used the NESN app or NBC Sports so I don't know if they have deeplinks or not off hand.
The "tuners" are fed to CDVR and it can send it back out over the network to all your client at the same time if you wish with that single "tuner"

1 Like

How many tuners you want? I usually want 2 for stuff like this as I'm not gonna record often and only have 2 TVs in the house. A single tuner LinkPi and a cheap HDMI usb encoder could do

What is your current or proposed setup? Cable? Satellite? Youtube TV? Fubo? Hulu Live?
What Services are you using or planning on using for watching tv?

Honestly it's likely just NESN and Boston Sports Net which I think I have issues with. Four would be the absolute max because typically I would pull up the four screens in a 2x2 grid.

What do you mean by a cheap HDMI USB encoder? Why couldn't you use many of these cheaper devices instead of a Linkpi?

Right now I use Channels DVR primarily to output a m3u playlist and epg that I feed into Tivimate. It works well to pull in my TVE channels from my Youtube TV subscription, (2) HD Homeruns (one towards Boston, one towards Portland, ME), Pluto and the MLB package.

Tivimate lets me display many screens on my 120" projector screen which is great for sports. The issue I have no is the Celtics (NBC Sports Boston) and Bruins/Red Sox (NESN) can no longer be pulled into Channels.

I haven't looked at Tivimate in a long time. Is it able to pull the Youtube TV channels direcetly in?

I never used the usb encoders with channels. But if you have the computer already running it would be a low cost way to try. Personally I prefer the stand alone linkpi or other stand alone encoder. Many are designed to run 24/7 including the LinkPi and at least for me it has been rock solid. More and more tv channels are doing away with their TVE intergration. Youtube TV through ADBTuner or AH4C is pretty solid so that would help future proof the rest of the youtube tv channels even when they drop TVE support.
If you are receiving the NBC Sports and NESN channels through Youtube TV then they would be able to be tuned like any other youtube tv channel and would not be any issues.

If you what to experiment on the cheap then a <$20 usb encoder and <$30 android tv dongle though I prefer the more expensive ones with ethernet included. Plus a computer to run ADBTuner on possibly the same one currently running channels.
Also it might be important to you to know that basically none of the encoder options are going to encode closed captions so you can turn them on and off on the fly or encode audio in anything more than stereo.

Just wanted to give an update after the nearly 2 months since we were all discussing this issue.

As I mentioned previously, I manually set the variable value via local use of adbtools. Since then, I have replaced the "other" 2 chromecasts with new ones (upgraded to 4k versions) and I purposely left them default w.r.t. the adb_allowed_connection_time thing in order to allow the upgraded app send the command. Both sets have not had a re-auth popup in all of this time. So...at least for my particular experience...I'm calling this "mission accomplished". Thanks for all the time you continue to put into this project..it has become my primary source to feed Channels for non-OTA programming and this quality-of-life improvement has made a measureable impact on both my enjoyment of the product as well as my sanity now that I don't have to babysit the chromecasts on seemingly a nearly hourly basis to ensure that they are not hung on a re-auth prompt.

1 Like

I don't think anyone can really answer this. Will it work forever? No. Will it work for a few years? Probably.

However, any purchased HDMI hardware could most certainly be repurposed to a non-android solution if that becomes a necessity.

If you use tuner sharing in Channels DVR and load the channels through Channels DVR I believe this should work.

I have quite the problem here. I have ADBTuner set up just fine with the latest version/container running through Docker Desktop for windows (also using the latest version of Docker Desktop for Windows). I'm using a LinkPi Encoder box along with an Onn Google TV 4k (these are also up-to-date with the latest updates/firmware). From what I can tell I also have all of these things properly set up from reading through all the forums here and following the instructions on the docker hub adbtuner page (device is in developer mode, not connected to any other adb devices, device is set to always have the screen on and not go to sleep, device was approved for an adb connection, etc.)

When I go to my ADBTuner webpage, it will show that the endpoint connection was successful, the onn device is running Android 12, and it's available. So finally getting to my main point here, when I select a channel to play in "Channels", here is what I typically get first:


followed by:

You'll notice that it says pressing play to try again, and after like tons of times doing this, everything finally seems to work and the channel will play. But what is weird here is, while I'm getting these tuner failure messages and connection lost messages, my ADBTuner webpage will show the little blue icon at the top that it's locked into whatever channel I had selected on "Channels". And what's even weirder is, I'll login to my settings for the LinkPi Encoder box and the preview will actually show the channel playing on the onn Google TV 4k device. Here are some examples of what my logs look like on channels when these error messages are displaying:

2024/08/27 12:48:27.464578 [ERR] Could not start stream for M3U-ADBTuner ch52 Discovery Family: M3U: 901 Tuner Unreachable: Timeout after 20.01s connecting to: http://127.0.0.1:5592/stream/22
2024/08/27 12:48:57.350845 [ERR] Could not start stream for M3U-ADBTuner ch52 Discovery Family: M3U: 901 Tuner Unreachable: Timeout after 27.32s connecting to: http://127.0.0.1:5592/stream/22
2024/08/27 12:51:19.573926 [ERR] Could not start stream for M3U-ADBTuner ch52 Discovery Family: M3U: 901 Tuner Unreachable: Timeout after 20s connecting to: http://127.0.0.1:5592/stream/22
2024/08/27 12:51:34.254659 [ERR] Could not start stream for M3U-ADBTuner ch52 Discovery Family: M3U: GET: http://127.0.0.1:5592/stream/22: 404 Not Found
2024/08/27 12:51:47.067588 [ERR] Could not start stream for M3U-ADBTuner ch52 Discovery Family: M3U: GET: http://127.0.0.1:5592/stream/22: 404 Not Found
2024/08/27 12:52:02.100969 [ERR] Could not start stream for M3U-ADBTuner ch52 Discovery Family: M3U: GET: http://127.0.0.1:5592/stream/22: 404 Not Found
2024/08/27 12:52:26.702101 [ERR] Could not start stream for M3U-ADBTuner ch52 Discovery Family: M3U: GET: http://127.0.0.1:5592/stream/22: 404 Not Found
2024/08/27 12:54:18.205539 [ERR] Could not start stream for M3U-ADBTuner ch52 Discovery Family: M3U: 901 Tuner Unreachable: Timeout after 20.01s connecting to: http://127.0.0.1:5592/stream/22
2024/08/27 12:54:31.349617 [ERR] Could not start stream for M3U-ADBTuner ch52 Discovery Family: M3U: GET: http://127.0.0.1:5592/stream/22: 404 Not Found
2024/08/27 12:55:43.116533 [ERR] Could not start stream for M3U-ADBTuner ch50 Story TV: M3U: 901 Tuner Unreachable: Timeout after 28.51s connecting to: http://127.0.0.1:5592/stream/20
2024/08/27 12:56:11.330325 [ERR] Could not start stream for M3U-ADBTuner ch50 Story TV: M3U: 901 Tuner Unreachable: Timeout after 22.57s connecting to: http://127.0.0.1:5592/stream/20

And here are examples of what I'm getting in the logs of my adbtuner container in docker:
2024-08-27 15:16:08 2024-08-27 19:16:05.188 - server - No tuners available.
2024-08-27 15:16:08 2024-08-27 19:16:08.247 - server - No tuners available.
2024-08-27 15:16:08 2024-08-27 19:16:08.443 - uvicorn.access - 172.17.0.1:41962 - "GET /stream/12 HTTP/1.1" 404
2024-08-27 15:16:27 2024-08-27 19:16:27.097 - server - No tuners available.
2024-08-27 15:16:27 2024-08-27 19:16:27.098 - uvicorn.access - 172.17.0.1:60332 - "GET /stream/12 HTTP/1.1" 404
2024-08-27 15:16:51 2024-08-27 19:16:51.594 - server - No tuners available.
2024-08-27 15:16:51 2024-08-27 19:16:51.595 - uvicorn.access - 172.17.0.1:54392 - "GET /stream/12 HTTP/1.1" 404
2024-08-27 15:17:17 2024-08-27 19:17:17.523 - server - No tuners available.
2024-08-27 15:17:18 2024-08-27 19:17:17.524 - uvicorn.access - 172.17.0.1:59780 - "GET /stream/12 HTTP/1.1" 404
2024-08-27 15:17:40 2024-08-27 19:17:40.660 - server - No tuners available.
2024-08-27 15:17:40 2024-08-27 19:17:40.661 - uvicorn.access - 172.17.0.1:34014 - "GET /stream/11 HTTP/1.1" 404
2024-08-27 15:19:01 2024-08-27 19:19:01.514 - uvicorn.access - 172.17.0.1:40256 - "GET / HTTP/1.1" 200
2024-08-27 15:19:07 2024-08-27 19:19:03.319 - uvicorn.access - 172.17.0.1:40254 - "GET /js/main.js?t=1724786335 HTTP/1.1" 200
2024-08-27 15:19:07 2024-08-27 19:19:03.392 - uvicorn.access - 172.17.0.1:40256 - "GET /css/style.css?t=1724786335 HTTP/1.1" 200
2024-08-27 15:19:20 2024-08-27 19:19:20.137 - lib.adb - ADB: Connecting to device (attempt 1).
2024-08-27 15:19:20 2024-08-27 19:19:20.158 - lib.adb - ADB: Connecting to device (attempt 1).
2024-08-27 15:19:21 2024-08-27 19:19:21.666 - lib.adb - ADB: Connected.
2024-08-27 15:19:21 2024-08-27 19:19:21.695 - lib.adb - ADB: Connected.
2024-08-27 15:19:21 2024-08-27 19:19:21.697 - lib.adb - ADB: 192.168.1.31 - settings list global
2024-08-27 15:19:21 2024-08-27 19:19:21.697 - lib.adb - ADB: 192.168.1.31 - settings list global
2024-08-27 15:19:22 2024-08-27 19:19:22.066 - root - adb_allowed_connection_time=0, adb_enabled=1, adb_wifi_enabled=0
2024-08-27 15:19:22 2024-08-27 19:19:22.117 - lib.adb - ADB: 192.168.1.31 - pm list packages -3
2024-08-27 15:19:22 2024-08-27 19:19:22.394 - root - adb_allowed_connection_time=0, adb_enabled=1, adb_wifi_enabled=0
2024-08-27 15:19:22 2024-08-27 19:19:22.413 - lib.adb - ADB: 192.168.1.31 - dumpsys display
2024-08-27 15:19:24 2024-08-27 19:19:24.061 - uvicorn.access - 172.17.0.1:60732 - "GET /admin/tuner/installed-packages?=1724786343678 HTTP/1.1" 200
2024-08-27 15:19:24 2024-08-27 19:19:24.554 - lib.adb - ADB: 192.168.1.31 - getprop ro.build.version.release
2024-08-27 15:19:24 2024-08-27 19:19:24.658 - lib.adb - ADB: Android Version: 12.0.
2024-08-27 15:19:38 2024-08-27 19:19:38.236 - httpx - HTTP Request: GET http://192.168.1.26:8090/stream0 "HTTP/1.0 200 OK"
2024-08-27 15:19:38 2024-08-27 19:19:38.563 - uvicorn.access - 172.17.0.1:40254 - "GET /admin/tuner/metadata?
=1724786343676 HTTP/1.1" 200
2024-08-27 15:20:06 2024-08-27 19:20:06.660 - server - No tuners available.
2024-08-27 15:20:06 2024-08-27 19:20:06.661 - uvicorn.access - 172.17.0.1:60656 - "GET /stream/11 HTTP/1.1" 404

What's even stranger about all of this is, I decided to remove the tuner from my ADBTuner webpage, and then re-add it, and right after I do that, everything works flawlesly. But then usually after 15 minutes or something, it goes right back to all these errors again. I'm at a complete loss here for what's going on. I've reset/powered off/restarted/unplugged etc. just about every device imaginable including my router and still no luck. I'm really hoping someone else on here has experienced this problem and figured out a fix or knows what exactly is going on. Really appreciate any help that can be provided. Thanks!

I'm not certain if you may be having the same problem I had when I set up my LinkPi and ADBTuner. The LinkPi comes with RTSP streams configured as default. Here's a link to my post with how I managed to get my hardware communicating properly: :smiley:

1 Like

Post your config for the linkpi, adbtuner, and channels and we can all take a look

I have mine set up exactly how you describe as well. I have just the one port LinkPi so only one onn Google TV 4k plugged into it. But my settings are exactly the same as yours including having MPEG-TS in the Channels source settings. The only minor difference I see...not really sure if it makes a difference...is that it looks like you have a sub stream turned on where as I do not:

Here are my linkpi configurations...I tried to attach as many screen captures as possible:












Here are my channels configurations:

And finally, my ADBTuner configurations:



One thing I do notice about the ADBTuner configs is the streaming endpoint address does not have /live in it but includes a port which I remember reading a post somewhere in this forum about doing it that way for the ADBTuner settings. But maybe that actually is the problem? I should also point out that for all the channels I have listed under my ADBTuner configurations, I have compatibility mode selected.

2 Likes

I turned the sub-stream off after reviewing my settings later. It works fine without it.

Have you verified that you have the correct play url entered as your streaming endpoint in ADBTuner? Yours looks to be http://192.168.1.26/live/stream0