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

I found my error. I updated the tag on an existing container in portainer and that's when it didn't show "add tuner".
I added a new container and it's there now.
Thanks

I did identify a situation where tuner data configured in an older version of ADBTuner could generate an error that would result in a failure to load any content in that part of the page.

I just pushed a new development version (20260721-1) with some additional data verification at startup to help avoid this in the future. It will also display an error there instead of nothing at all.

Thanks for letting me know you ran into this.

1 Like

From the logs, as far as ADBTuner is concerned, the streaming client completely closed the connection and a new connection was not opened for that channel in less than 8 seconds so it tore down the tuner.

I'm not sure exactly what you are up to over there, but if you need a longer window you can set the KNOWN_STREAM_DEFAULT_TIMEOUT environment variable to something larger than 8.

I am running into a strange bug where just a tuner won't release, essentially.

It'll just keep a tuner open indefinitely on the latest development build. I haven't tried the one that you pushed today, but as of yesterday's build, this keeps happening.

Interestingly enough, if I open that channel back up, it will pick back up on that exact tuner.

I must have hit perfect timing because I ended up on 20260721-2. In that one, I am not seeing configurations in the interface, but they are definitely there because I can play the stations:

Also saw this in the logs...
2026-07-21 16:51:34.351 | 2026-07-21 16:51:34.348 - uvicorn.access - 172.26.0.1:0 - "GET /api/v1/configurations HTTP/1.1" 500
2026-07-21 16:51:34.435 | ERROR:    Exception in ASGI application
2026-07-21 16:51:34.435 | Traceback (most recent call last):
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/uvicorn/protocols/http/httptools_impl.py", line 421, in run_asgi
2026-07-21 16:51:34.435 |     result = await app(  # type: ignore[func-returns-value]
2026-07-21 16:51:34.435 |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21 16:51:34.435 |         self.scope, self.receive, self.send
2026-07-21 16:51:34.435 |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21 16:51:34.435 |     )
2026-07-21 16:51:34.435 |     ^
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/uvicorn/middleware/proxy_headers.py", line 63, in __call__
2026-07-21 16:51:34.435 |     return await self.app(scope, receive, send)
2026-07-21 16:51:34.435 |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/fastapi/applications.py", line 1159, in __call__
2026-07-21 16:51:34.435 |     await super().__call__(scope, receive, send)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/applications.py", line 90, in __call__
2026-07-21 16:51:34.435 |     await self.middleware_stack(scope, receive, send)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/middleware/errors.py", line 186, in __call__
2026-07-21 16:51:34.435 |     raise exc
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/middleware/errors.py", line 164, in __call__
2026-07-21 16:51:34.435 |     await self.app(scope, receive, _send)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/middleware/exceptions.py", line 63, in __call__
2026-07-21 16:51:34.435 |     await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
2026-07-21 16:51:34.435 |     raise exc
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
2026-07-21 16:51:34.435 |     await app(scope, receive, sender)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
2026-07-21 16:51:34.435 |     await self.app(scope, receive, send)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/routing.py", line 660, in __call__
2026-07-21 16:51:34.435 |     await self.middleware_stack(scope, receive, send)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/routing.py", line 680, in app
2026-07-21 16:51:34.435 |     await route.handle(scope, receive, send)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/routing.py", line 276, in handle
2026-07-21 16:51:34.435 |     await self.app(scope, receive, send)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/fastapi/routing.py", line 134, in app
2026-07-21 16:51:34.435 |     await wrap_app_handling_exceptions(app, request)(scope, receive, send)
2026-07-21 16:51:34.435 |   File "/app/.venv/lib/python3.14/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
2026-07-21 16:51:34.435 |     raise exc
2026-07-21 16:51:34.436 |   File "/app/.venv/lib/python3.14/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
2026-07-21 16:51:34.436 |     await app(scope, receive, sender)
2026-07-21 16:51:34.436 |   File "/app/.venv/lib/python3.14/site-packages/fastapi/routing.py", line 120, in app
2026-07-21 16:51:34.436 |     response = await f(request)
2026-07-21 16:51:34.436 |                ^^^^^^^^^^^^^^^^
2026-07-21 16:51:34.436 |   File "/app/.venv/lib/python3.14/site-packages/fastapi/routing.py", line 695, in app
2026-07-21 16:51:34.436 |     content = await serialize_response(
2026-07-21 16:51:34.436 |               ^^^^^^^^^^^^^^^^^^^^^^^^^
2026-07-21 16:51:34.436 |     ...<11 lines>...
2026-07-21 16:51:34.436 |     )
2026-07-21 16:51:34.436 |     ^
2026-07-21 16:51:34.436 |   File "/app/.venv/lib/python3.14/site-packages/fastapi/routing.py", line 300, in serialize_response
2026-07-21 16:51:34.436 |     raise ResponseValidationError(
2026-07-21 16:51:34.436 |     ...<3 lines>...
2026-07-21 16:51:34.436 |     )
2026-07-21 16:51:34.436 | fastapi.exceptions.ResponseValidationError: 16 validation errors:
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 4, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1900-0000-0000-AEHistoryFYI', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 5, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1100-0000-0000-CBS000000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 6, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1160-0000-0000-CBSSports000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 7, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1052-0000-0000-CNBC00000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 8, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1800-0000-0000-CNN000000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 9, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-3000-0000-0000-DisneyPlus00', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 10, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1500-0000-0000-ESPN00000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 11, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1300-0000-0000-FOXOne000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 12, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1700-0000-0000-HGTV00000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 13, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1960-0000-0000-Lifetime0000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 14, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1051-0000-0000-MSNOW0000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 15, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1000-0000-0000-NBC000000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 16, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1600-0000-0000-NFL000000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 17, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1400-0000-0000-PBS000000000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 18, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1499-0000-0000-PBSKids00000', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 |   {'type': 'uuid_parsing', 'loc': ('response', 19, 'uuid'), 'msg': 'Input should be a valid UUID, invalid character: found `p` at 3', 'input': '0AppPlay-1805-0000-0000-TBSTNTtruTV0', 'ctx': {'error': 'invalid character: found `p` at 3'}}
2026-07-21 16:51:34.436 | 
2026-07-21 16:51:34.436 |   File "/app/app/routers/rest.py", line 325, in list_configurations
2026-07-21 16:51:34.436 |     GET /api/v1/configurations

Something glorious! :stuck_out_tongue_winking_eye:

Of course, I ran another test after I posted and it went for over an hour with no issue. Well, "no" isn't exactly right.

I keep seeing drops and pickups in Channels like this...
2026/07/21 17:06:03.746785 [HLS] Couldn't generate stream playlist for ch1110-dANY-ip172.22.0.1: Couldn't find playlist: /shares/DVR/Streaming/sessions/ch1110-dANY-ip172.22.0.1-3696814256/remux/stream.m3u8: stat /shares/DVR/Streaming/sessions/ch1110-dANY-ip172.22.0.1-3696814256/remux/stream.m3u8: no such file or directory
2026/07/21 17:06:03.767471 [HLS] Stopping transcoder session ch1110-dANY-ip172.22.0.1 (out=15m37.112s finished=false first_seq=1 last_seq=935)
2026/07/21 17:06:03.848541 [HTTP] | 200 |        14m35s |       127.0.0.1 | POST     "/hls/progress?key=ch1110-dANY-ip172.22.0.1-remux"
2026/07/21 17:06:03.891926 [TNR] Closed connection to M3U-PLMGracenoteFallbackMPEGTS01 for ch1110 CNN
2026/07/21 17:06:03.946330 [SNR] Buffer statistics for ch1110 CNN: buf=0%-1% drop=0%
2026/07/21 17:06:04.705437 [HTTP] | 404 |    1.355353ms |      172.22.0.1 | PUT      "/devices/ANY/channels/1110/hls"
2026/07/21 17:06:06.716731 [HTTP] | 200 |    3.346903ms |      172.22.0.1 | PUT      "/devices/ANY/channels/1110/hls"
2026/07/21 17:06:07.986369 [WARN] Stopped stream ch1110-dANY-ip172.22.0.1 took 4.226654307s
2026/07/21 17:06:07.996437 [HTTP] | 500 |    4.4168243s |      172.22.0.1 | GET      "/devices/ANY/channels/1110/hls/stream.m3u8?abr=false&acodec=copy&bitrate=588&indexed=true&ssize=1&vcodec=copy"
2026/07/21 17:06:08.700755 [HTTP] | 200 |     691.038µs |      172.22.0.1 | PUT      "/devices/ANY/channels/1110/hls"
...
2026/07/21 17:06:14.574764 [HTTP] | 200 |    1.026799ms |      172.22.0.1 | PUT      "/devices/ANY/channels/1110/hls"
2026/07/21 17:06:16.487315 [HTTP] | 200 |    1.456943ms |      172.22.0.1 | PUT      "/devices/ANY/channels/1110/hls"
2026/07/21 17:06:16.869915 [TNR] Opened connection to M3U-PLMGracenoteFallbackMPEGTS01 for ch1110 CNN
2026/07/21 17:06:16.921139 [HLS] Starting live stream for channel 1110 from 172.22.0.1
2026/07/21 17:06:18.420414 [HTTP] | 200 |     510.054µs |      172.22.0.1 | PUT      "/devices/ANY/channels/1110/hls"
2026/07/21 17:06:19.122431 [HTTP] | 200 | 12.339547127s |      172.22.0.1 | GET      "/devices/ANY/channels/1110/hls/master.m3u8?abr=false"
2026/07/21 17:06:19.137837 [HLS] Probed live stream in 2.115437149s: h264 1920x1080 progressive 4288151bps
2026/07/21 17:06:20.261588 [HTTP] | 200 |     550.686µs |      172.22.0.1 | PUT      "/devices/ANY/channels/1110/hls"
2026/07/21 17:06:20.748684 [HLS] Session ch1110-dANY-ip172.22.0.1 started in 3.779336918s
2026/07/21 17:06:21.043924 [HTTP] | 200 |  1.891135431s |      172.22.0.1 | GET      "/devices/ANY/channels/1110/hls/stream.m3u8?abr=false"
2026/07/21 17:06:21.127975 [HTTP] | 200 |   49.071847ms |      172.22.0.1 | GET      "/devices/ANY/channels/1110/hls/stream.m3u8/stream2.ts?abr=false"
2026/07/21 17:06:21.203324 [HTTP] | 200 |   43.512269ms |      172.22.0.1 | GET      "/devices/ANY/channels/1110/hls/stream.m3u8/stream3.ts?abr=false"
2026/07/21 17:06:21.309013 [HTTP] | 200 |   34.914978ms |      172.22.0.1 | GET      "/devices/ANY/channels/1110/hls/stream.m3u8/stream4.ts?abr=false"

That loss of the ADBTuner stream and re-connection time is what is being registered in ADBTuner as disconnect. I'm not sure if it is ADBTuner skipping a segment or Channels/VLC missing one, but the result is the same.

I did so...

image
image

Based on the logs...
2026-07-21 17:05:43.709 | --------------------------------------------------
2026-07-21 17:05:43.709 | Tuner "Link Pi ENC1-V3" is currently in use and locked.
2026-07-21 17:05:43.709 | Tune ID: WMfqdaBaDp9Zc34YuiADae
2026-07-21 17:05:43.709 | Channel: CNN
2026-07-21 17:05:43.709 | Lock Obtained: 2026-07-21 16:50:24 (15.33 minutes ago)
2026-07-21 17:05:43.709 | Last Seen: 2026-07-21 17:05:42 (1.65 seconds ago)
2026-07-21 17:05:43.709 | Bytes Transferred: 484418284 (461.98MB)
2026-07-21 17:05:43.709 | Remote User Agent:
2026-07-21 17:05:43.709 | channels-dvr/2026.07.18.0414 Go-http-client/1.1 
2026-07-21 17:05:43.709 | (linux-x86_64)
2026-07-21 17:05:43.709 | --------------------------------------------------
2026-07-21 17:06:14.705 | 2026-07-21 17:06:14.609 - stream - [Tune T4fJFXCNGj9k4LPoQroYZC] 10.255.1.144:7968 | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
2026-07-21 17:06:15.371 | 2026-07-21 17:06:15.355 - stream - [Tune (T4fJFXCNGj9k4LPoQroYZC)] Tuner (CNN) is already tuned to CNN. Connecting to this stream.
2026-07-21 17:06:15.386 | 2026-07-21 17:06:15.368 - uvicorn.access - 172.26.0.1:0 - "GET /stream/81 HTTP/1.1" 307
2026-07-21 17:06:15.434 | 2026/07/21 17:06:15 [PROXY] 172.26.0.1 -> GET "/proxy/1?requestKey=WMfqdaBaDp9Zc34YuiADae" -> "http://10.255.1.106:8090/hdmi1"
2026-07-21 17:06:15.667 | 2026/07/21 17:06:15 [PROXY] 172.26.0.1 -> GET "/proxy/1?requestKey=WMfqdaBaDp9Zc34YuiADae" -> "http://10.255.1.106:8090/hdmi1"
2026-07-21 17:06:15.902 | 2026/07/21 17:06:15 [PROXY] Keyframe found for key WMfqdaBaDp9Zc34YuiADae (buffered 210496 bytes, flushing 13472 bytes with PAT/PMT)
2026-07-21 17:06:15.927 | 2026-07-21 17:06:15.926 - stream - [Tune h54bUT5aKGAeXxxCo3hvwu] 10.255.1.144:7968 | Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
2026-07-21 17:06:15.942 | 2026-07-21 17:06:15.942 - stream - [Tune (h54bUT5aKGAeXxxCo3hvwu)] Tuner (CNN) is already tuned to CNN. Connecting to this stream.
2026-07-21 17:06:15.944 | 2026-07-21 17:06:15.943 - uvicorn.access - 172.26.0.1:0 - "GET /stream/81 HTTP/1.1" 307
2026-07-21 17:06:16.003 | 2026/07/21 17:06:16 [PROXY] 172.26.0.1 -> GET "/proxy/1?requestKey=WMfqdaBaDp9Zc34YuiADae" -> "http://10.255.1.106:8090/hdmi1"
2026-07-21 17:06:16.229 | 2026-07-21 17:06:16.228 - server - 
2026-07-21 17:06:16.229 | 
2026-07-21 17:06:16.229 | --------------------------------------------------
2026-07-21 17:06:16.229 | Tuner "Link Pi ENC1-V3" is currently in use and locked.
2026-07-21 17:06:16.229 | Tune ID: WMfqdaBaDp9Zc34YuiADae
2026-07-21 17:06:16.229 | Channel: CNN
2026-07-21 17:06:16.229 | Lock Obtained: 2026-07-21 16:50:24 (15.87 minutes ago)
2026-07-21 17:06:16.229 | Last Seen: 2026-07-21 17:06:16 (0.22 seconds ago)
2026-07-21 17:06:16.229 | Bytes Transferred: 101708 (0.1MB)
2026-07-21 17:06:16.229 | Remote User Agent:
2026-07-21 17:06:16.229 | Mozilla/5.0 
2026-07-21 17:06:16.229 | (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 
2026-07-21 17:06:16.229 | (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
2026-07-21 17:06:16.229 | --------------------------------------------------
2026-07-21 17:06:16.560 | 2026/07/21 17:06:16 [PROXY] No keyframe found within 500ms for key WMfqdaBaDp9Zc34YuiADae (253988 bytes buffered), flushing buffer
2026-07-21 17:06:16.702 | 2026-07-21 17:06:16.702 - stream - [Tune aX3QPZrZtcgbNDVNifVejU] 10.255.1.144:7968 | channels-dvr/2026.07.18.0414 Go-http-client/1.1 (linux-x86_64)
2026-07-21 17:06:16.704 | 2026-07-21 17:06:16.704 - stream - [Tune (aX3QPZrZtcgbNDVNifVejU)] Tuner (CNN) is already tuned to CNN. Connecting to this stream.
2026-07-21 17:06:16.705 | 2026-07-21 17:06:16.705 - uvicorn.access - 172.26.0.1:0 - "GET /stream/81 HTTP/1.1" 307
2026-07-21 17:06:16.773 | 2026/07/21 17:06:16 [PROXY] 172.26.0.1 -> GET "/proxy/1?requestKey=WMfqdaBaDp9Zc34YuiADae" -> "http://10.255.1.106:8090/hdmi1"
2026-07-21 17:06:16.822 | 2026/07/21 17:06:16 [PROXY] Keyframe found for key WMfqdaBaDp9Zc34YuiADae (buffered 15620 bytes, flushing 15620 bytes with PAT/PMT)
2026-07-21 17:06:48.680 | 2026-07-21 17:06:48.653 - server - 
2026-07-21 17:06:48.683 | 
2026-07-21 17:06:48.683 | --------------------------------------------------
2026-07-21 17:06:48.683 | Tuner "Link Pi ENC1-V3" is currently in use and locked.
2026-07-21 17:06:48.683 | Tune ID: WMfqdaBaDp9Zc34YuiADae
2026-07-21 17:06:48.683 | Channel: CNN
2026-07-21 17:06:48.683 | Lock Obtained: 2026-07-21 16:50:24 (16.41 minutes ago)
2026-07-21 17:06:48.683 | Last Seen: 2026-07-21 17:06:47 (1.63 seconds ago)
2026-07-21 17:06:48.683 | Bytes Transferred: 17000652 (16.21MB)
2026-07-21 17:06:48.683 | Remote User Agent:
2026-07-21 17:06:48.683 | channels-dvr/2026.07.18.0414 Go-http-client/1.1 
2026-07-21 17:06:48.683 | (linux-x86_64)
2026-07-21 17:06:48.683 | --------------------------------------------------

... I believe it worked because there's more than 8 seconds from when the disconnect happened above and when the process reconnected to ADBTuner.

This also appeared true when I manually disconnected later...
2026-07-21 17:55:55.331 | --------------------------------------------------
2026-07-21 17:55:55.331 | Tuner "Link Pi ENC1-V3" is currently in use and locked.
2026-07-21 17:55:55.331 | Tune ID: WMfqdaBaDp9Zc34YuiADae
2026-07-21 17:55:55.331 | Channel: CNN
2026-07-21 17:55:55.331 | Lock Obtained: 2026-07-21 16:50:24 (65.52 minutes ago)
2026-07-21 17:55:55.331 | Last Seen: 2026-07-21 17:55:54 (1.32 seconds ago)
2026-07-21 17:55:55.331 | Bytes Transferred: 1351727520 (1289.11MB)
2026-07-21 17:55:55.331 | Remote User Agent:
2026-07-21 17:55:55.331 | channels-dvr/2026.07.18.0414 Go-http-client/1.1 
2026-07-21 17:55:55.331 | (linux-x86_64)
2026-07-21 17:55:55.331 | --------------------------------------------------
2026-07-21 17:56:27.539 | 2026-07-21 17:56:27.538 - server - 
2026-07-21 17:56:27.541 | 
2026-07-21 17:56:27.541 | --------------------------------------------------
2026-07-21 17:56:27.541 | Tuner "Link Pi ENC1-V3" is currently in use and locked.
2026-07-21 17:56:27.541 | Tune ID: WMfqdaBaDp9Zc34YuiADae
2026-07-21 17:56:27.541 | Channel: CNN
2026-07-21 17:56:27.541 | Lock Obtained: 2026-07-21 16:50:24 (66.06 minutes ago)
2026-07-21 17:56:27.541 | Last Seen: 2026-07-21 17:55:56 (31.53 seconds ago)
2026-07-21 17:56:27.541 | Bytes Transferred: 1352834652 (1290.16MB)
2026-07-21 17:56:27.541 | Remote User Agent:
2026-07-21 17:56:27.541 | channels-dvr/2026.07.18.0414 Go-http-client/1.1 
2026-07-21 17:56:27.541 | (linux-x86_64)
2026-07-21 17:56:27.541 | --------------------------------------------------
2026-07-21 17:56:37.253 | 2026-07-21 17:56:37.253 - tuner_management - Releasing tuner 1 as it is no longer tracked by the proxy server.
2026-07-21 17:56:37.481 | 2026-07-21 17:56:37.481 - tuner_management - [Tune WMfqdaBaDp9Zc34YuiADae] Using configuration: App Play - CNN (babsonnexus) (0AppPlay-1800-0000-0000-CNN000000000) for channel cleanup.
2026-07-21 17:56:37.483 | 2026-07-21 17:56:37.483 - tuner_management - Post tune commands: [{'type': 'ADB', 'command': 'input keyevent KEYCODE_MEDIA_STOP'}, {'type': 'ADB', 'command': 'input keyevent KEYCODE_MEDIA_PAUSE'}, {'type': 'ADB', 'command': 'input keyevent KEYCODE_HOME'}, {'type': 'ADB', 'command': "am force-stop 'com.cnn.mobile.android.tv'"}]
2026-07-21 17:56:37.485 | 2026-07-21 17:56:37.485 - lib.adb - [Pool 1] ADB: 10.255.1.168 - input keyevent KEYCODE_MEDIA_STOP
2026-07-21 17:56:37.624 | 2026-07-21 17:56:37.623 - lib.adb - [Pool 1] ADB: 10.255.1.168 - input keyevent KEYCODE_MEDIA_PAUSE
2026-07-21 17:56:37.820 | 2026-07-21 17:56:37.819 - lib.adb - [Pool 1] ADB: 10.255.1.168 - input keyevent KEYCODE_HOME
2026-07-21 17:56:37.980 | 2026-07-21 17:56:37.980 - lib.adb - [Pool 1] ADB: 10.255.1.168 - am force-stop 'com.cnn.mobile.android.tv'
2026-07-21 17:56:38.351 | 2026-07-21 17:56:38.351 - tuner_management - [Tune WMfqdaBaDp9Zc34YuiADae] Released tuner (1).
2026-07-21 17:56:38.352 | 2026-07-21 17:56:38.351 - tuner_management - [Tune WMfqdaBaDp9Zc34YuiADae] Cleanup complete. Link Pi ENC1-V3 (CNN)
1 Like

Can you try shutting down Channels DVR and see if the tuners release? I ran into this once a few days ago and it seemed as though Channels was keeping the connection open.

ADBTuner tries to share tuners so that resume behavior is expected.

I'm seeing this in the log, everytime I start the container. Using stable tag. Is this anything to be concerned about?

ERROR [alembic.util.messaging] Can't locate revision identified by 'c9f3aa41b7e2'
FAILED: Can't locate revision identified by 'c9f3aa41b7e2'

It looks like you tried the development version and then rolled back to stable? You can ignore that message for now. It will go away whenever you upgrade to a newer version in the future.

2 Likes

I pushed a small update to the stable build (20260723-1) this morning. It fixes a rare crash that could occur if an Android device goes offline (ex. reboots) in the middle of a tuning operation.

I also pushed an update to the development version (20260723-2) that fixes a bug that could cause the configuration clone feature to fail.

I pushed a new :development build this morning (20260724-10).

This build adds a long overdue Backup & Restore feature that can be used to back up your entire ADBTuner configuration (tuners, channels, configurations, etc.) into a single file.

Important Note
As part of this change, custom channel configurations are no longer stored on the filesystem. Any channel configuration files stored on the filesystem are automatically moved to the app's database during a one-time process on app startup.

Note: For now, these JSON channel configuration files are being left in place to allow users to roll back to the stable build if they wish. However, any configuration changes made in the development build prior to rolling back will be lost.

The web interface and API provide tools to import and export configurations individually or in bulk.

Additional Notes
Channel configurations can no longer be deleted if configured channels link to that configuration. This prevents the silent breakage that occurred before. This includes channel configurations that are being synced from a remote repository. The entire repository sync will be blocked if that sync would break a configured channel.

Anyone using the API or building integrations with ADBTuner should be aware that there is more type checking and validation in place now. This applies to API queries and the content within configurations. Configuration filenames don't matter, but the content within should all be valid.

Some additional API endpoints were added. Please review /api/docs for more info. Since the admin frontend now uses this API you should be able to do anything the admin interface can do via the API.

I did take a look at OliveTin, Project One-Click, and HDMI Encoder Native Apps and added some shims to make sure the existing integrations will continue to work. I apologize if this change causes any issues or makes any additional work for anyone.

Thanks everyone for testing and please report any issues. Thanks!

2 Likes

Hey turtletank! I wanted to thank you for those "timed keep active commands". They ended up being the lever I needed to pull to get my Osprey fleet working again. I was about to give up on Channels completely and just use an Osprey box natively because I could not for the life of me figure this out. What ended up solving it was Opus 5 which just came out yesterday and decompiling the APKs right off the box. It was able to figure out the code path causing the issue and propose a solution. And it ended up being completely right. ™️

Long story short, it seems like there's some sort of server side flag that gets flipped by DirecTV. When either a deep link or channel # tune using ah4c is fired using adb the DirecTV app doesn't register that there was human input. The act of waking it from sleep and sending a command over ADB is what triggers this path. There are actually two flags in the source code. UI_TIMEOUT_OSPREY is the inactivity timer itself, and USE_YOURTV_FOR_STARTUP is what makes it restart playback instead of just dismissing the UI. You need both of them on. They're rolling out slowly to DirecTV users and Osprey boxes probably at random and I must have been the lucky one that got selected. What actually happens is when you sleep the box the app marks playback as terminated for its own "Your TV" startup flow, and nothing in the ADB wake ever clears that mark. So five minutes later the timer fires, it runs that startup flow, and it restores the last channel, which is the channel that was already playing. It's sort of like it thinks you left for Netflix and came back, except nobody ever left. That's what I'm gathering from the analysis I was able to do.

Regardless, sending a heartbeat every three minutes to push back the five minute timer has been working amazingly well and that little bit of json is such an elegant solution for this so much easier than scripting it with ah4c which at some point I'm going to have to do to support that project but I want to wait until it hits more people and see if maybe they pull this feature back.

I just wanted to ask is that feature pretty robust and reliable like do I have to worry about it potentially not firing or like the ADB connection dropping and it's not firing? If I miss a fire then it kind of messes up my stream for a little bit like I get like that blip.

"timed_keep_active_commands": [
        {
            "run_every_minutes": 3,
            "commands": [
                "input keyevent 211"
            ]
        }
    ]

Anyway, sending this random Japanese keystroke every three minutes has solved my problem like completely. It's disappeared, and ADBTuner is the easiest way to implement it by far. So as per usual, amazing work, thank you. And I'm really loving the new dev build and the new UI. It looks so great!!!

1 Like

I'm glad you found a solution. Yeah. Unfortunately, most of this stuff is quickly getting too tedius to even be worth the effort.

For what it's worth, those commands run every 3 minutes while a tuner is active. It was added to avoid "are you still watching?" prompts. The idea was to find a command that can be sent that would show activity, but not interrupt the active stream in any way. Those commands will fire every X minutes as long as the device is connected, but will stop when the tuner is released. I don't know if that solves your issue entirely.

1 Like

I've been watching since last night and it solves my issue entirely like it works perfectly for this use case of keeping these streams from reloading after 5 mins. I don't know why DirecTV would implement something so stupid but it works perfectly! Thank you!

This is one of those things that I more or less did for the love of the game lol. I really like Channels and the community here so I wanted to try to find a way to stick it out and find a solution. If this is something that's hitting my boxes eventually it's gonna hit other people too.

If the Osprey is looking for a lower level input, closer to a real, remote button press, would scrcpy register more?

Hi all!
I have been working on setting up a few channels using the HDMI Encoder Native Apps.

I have already successfully added my local CBS channel. Next up: PBS.

If I import the PBS channel list json, using the "Delete" option, will it only delete existing channels from the same input source? (PBS app) I'd hate to have it dump everything I have worked so hard to set up already.

Are you talking about ABDTuner's "Delete" option?

image

If so, it will delete ALL existing stations, so don't do that!

Or are you talking about OliveTin's "NATIVE_REMOVE" option?

That one only deletes the stations associated with that source app, so is more like a scalpel.

In either case, I'm not sure why you are looking to delete at all? If you loaded up CBS and now you want PBS, it is just an additive function. They don't overlap in any of the settings, so you can just import them in individually without any worries.

1 Like

My question was triggered by this issue which occurred while importing the PBS json. It skips the main PBS Local channel, identifying it as a duplicate. No such channel currently exists in my ADBT lineup.

Screenshot 2026-07-27 5.17.49 PM

I thought that it might be leftover Data from a previous attempt to add PBS. I had hoped to use "Delete" to remove the offending duplicate. Good thing I didn't do that. I'm glad to know of the "Native Remove" Olivetin function. Somehow, I never heard of that action.

Do you have any idea why I received the "skipped duplicate" message, and how I can successfully import both PBS channels in your PBS json?

There's a bug in ADBTuner that @turtletank needs to fix. I previously alerted him:

1 Like

This is exactly what is happening with PBS. Only the second channel "NHK World" is loaded.

I must have missed this conversation. So much has been changing lately, that it's hard for my aging mind to keep up... my :brain: = :poop: :laughing:

1 Like

I just pushed a new development build (20260728-1). One change, package name and alternate package name are now considered when determining if a "url" is a duplicate.

This should fix those channel list imports with duplicate values in the URL field.

1 Like