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

So, this was a me problem. I did something dumb and broke the HDMI handshaking on my boxes. I had to revert something that I did.

This is actually a situation where Raycast came in clutch. It has a really, really long history with the clipboard manager, and I was able to figure out what commands I ran by just kind of digging through my clipboard manager and reversing them essentially.

Now everything is working as it should! Basically spent two days chasing nothing. my own tail!

I did, however, find a bug with the playback delay feature where it kind of drains too quickly and everything goes into high speed mode as soon as the show starts playing. I have a PR open to fix that. so, hopefully, that gets merged soon and that feature will work properly for most people.

2 Likes

been there! :upside_down_face:

I went back in my stack logs and could find no reason for my box getting on 261 :man_shrugging: it's toggling between both usb captures on 2 C71KW-200's every half hour 16hrs a day so I do kinda expect a hiccup now and again, just be nice if i could see the ospreys' logs

I just got my linkpi and tested a lot of deep links I don't have access to directv login only my logged in acct on Android fromy ex most channels I found on this server load deep links. I also have uverse apk I have modded to run on Ethernet and work on other android devices ..it also has deep link functionality . There is so many setup options I am totally lost. Can anyone help me if I pay for some support? I can venmo

Come on, we've told you already. No one's taking Venmo from you...

To be honest, I would consider switching to deep links.

They tune a little bit quicker, and I find that especially with overflow channels, you can access them, whereas with channel numbers, it's impossible to tune an overflow. Another advantage is, I've never had a failed deep link tune, but I've had failed channel number tunes pretty repeatedly because if it tunes too soon after waking the box, it'll tune into nothing.

I actually, revised my readiness gate for the deep link script and also the wake script for that, and I might publish it, but I want to test it a little bit longer to ensure it doesn't lock up on anything.

I figured out the problems i was having with ah4c all of a sudden. I ended up downgrading my ENC5-V2 units to this older firmware, and suddenly they're working like beautifully. rootfs_20251130

Just an fyi, if anyone's having any weirdness, maybe try flashing down to this old firmware using the flash method. How to flash the device? - LINKPI HD VIDEO ENCODE - youyeetoo Geek shop

Some posts back, I laid out how to implement an approach to supporting multiple providers in a single ah4c container:

This has been formalized now through the WebUI. If you want your ah4c instance to be able to tune using multiple different apps, requiring multiple sets of scripts and a combined M3U -- it can all be done very easily by first creating an "all.m3u", and then setting STREAMER_APP=scripts/all/all.

Using the new Create all.m3u menu item, you can select the M3Us you want to combine, using which set of scripts:

Your CDVR Custom Channels Source will just need to reference this all.m3u (or whatever you chose to call it), and the correct set of scripts will be used for tuning.

1 Like

I may have to update the DirecTV Osprey scripts at some point. I'm going to wait a little bit to confirm that it doesn't regress, but it appears that the need for a heartbeat is no longer required, and they fixed that timeout that was occurring.

I noticed all of a sudden it just stopped happening with the heartbeat disabled. So, have some test scripts on my box right now that are working quite well. I even simplified prebmitune.sh to use less adb calls it's a small refactor. I also removed the readiness gate I had because it just wasn't needed anymore.

I found that the readiness gate is probably very good for the channel number tuning because it prevents tuning a channel into the void too quickly but for the deep linking, it's definitely not needed. I'm down to like 4 seconds again.

I'm probably going to sit on them for a couple of weeks and just be certain, and then open a PR to get them up there.

I'm also thinking it makes sense to rename heartbeat interval just back to keep watching way it's consistent with the rest of the scripts, and we can drop that environment variable from the Docker Compose and there's really no point to it when it effectively just becomes a really great invisible key code for preventing a timeout when watching TV for a really long time!

I started seeing posts on Reddit. I then started raising hell on Twitter with them. I ended up getting in touch with their office of the president and really made it clear that this wasn't anything specific to my setup, but many people were seeing this problem, just regular users on Reddit. so I didn't have to mention anything about our little community here. it was more of like, this is a major issue you guys have invented and need to fix. Clearly, some engineer figured it out and decided to watch TV for the first time, I guess :joy:.

1 Like

I'm vibe coding a uverse backup for DirecTV and a m3u8 that does baton handoff so that if both DirecTV and uverse are watching a channel it will use prismcast to tune

If you look, AH4C has something already that does this combined M3U.

It also explicitly does not support M3U8, that is for HLS streams. You are definitely in the wrong place. if you're looking for M3U8.

1 Like

I figured out the hls deeplinks for most channels. I will post them later with my uverse modded apk with deeplinks.

I am also incorporating with prismcast+ a-i-o. I have the newsmix and sportsmix working as well and I am making some channels have a fallback and black screen baton so streams don't end, if something goes down or is unresponsive it will go to the other source.

1 Like

Please consider not posting them here. This is the wrong thread. This has nothing to do with prism cast. You're in the AH4C thread. You're posting in the wrong place again, please.

I imagine sharing a modded U-verse APK goes against forum rules in terms of piracy and such.

Maybe make your own thread to post this stuff in if you want to post it rather than hijacking this one.

1 Like

Just put that dude in the ignore list. Hes a lost cause. He always spam the forums with nonsense.

Im surprised the mods haven't sent him on his way yet

1 Like

I'm actually more interested in reading what he has to say. It livens the mood a little bit when I see random nonsense. :grin:

We have a new beta (bnhf/ah4c:beta), for anyone to try who's interested. This is another excellent effort by @mackid1993, including a new in-app settings option (env vars continue to work, and take precedence), along with lots of other new features through the WebUI.

If you'd like to continue to use the "full" Docker Compose (which will require no new configuration on your part), here's the updated version:

services:
  # 2026.09.16
  # GitHub home for this project with setup instructions: https://github.com/sullrich/ah4c
  # Docker Hub home for this project: https://hub.docker.com/repository/docker/bnhf/ah4c
  ah4c: # This docker-compose typically requires no editing. Use the Environment variables section of Portainer to set your values.
    image: bnhf/ah4c:${TAG:-latest}
    container_name: ${CONTAINER_NAME:-ah4c}
    hostname: ${HOSTNAME:-ah4c}
    dns_search: ${DOMAIN:-localdomain} # Specify the name of your LAN's domain, usually local or localdomain
    runtime: ${DOCKER_RUNTIME:-runc} # Closed captions only. Set DOCKER_RUNTIME=nvidia for an NVIDIA GPU with the CUDA engine build. Requires the NVIDIA container toolkit.
    devices:
      - ${GPU_DEVICE:-/dev/null} # Closed captions only. Set GPU_DEVICE=/dev/dri to let the Vulkan engine build use an Intel or AMD GPU. Left at the default it passes /dev/null, which always exists and does nothing.
    ports:
      - ${HOST_PORT:-7654}:7654 # Port used by this ah4c proxy
    environment:
      - AH4C_COMPOSE=2026.09.16 # Compose file date stamp - do not change. The startup log flags it when out of date.
      # ── Proxy identity ──────────────────────────────────────────────────────
      - IPADDRESS=${IPADDRESS} # Hostname or IP address of this ah4c extension to be used in M3U file (also add port number if not in M3U)
      - SETUP_WIZARD=${SETUP_WIZARD:-false} # Full environment-managed installs skip the web setup wizard. Set to true to allow it.
      # ── Tuners ──────────────────────────────────────────────────────────────
      # One trio per tuner: TUNERn_IP is the box to tune; video comes from CMDn if set, otherwise ENCODERn_URL.
      - NUMBER_TUNERS=${NUMBER_TUNERS} # Number of tuners you'd like defined - add a matching TUNERn_IP, ENCODERn_URL and CMDn trio below for each beyond 9
      - TUNER1_IP=${TUNER1_IP} # Streaming device #1 with adb port in the form hostname:port or ip:port
      - ENCODER1_URL=${ENCODER1_URL} # Tuner #1 source URL (http://hostname/stream). Streamed directly when CMD1 is blank; otherwise referenced by CMD1 as ${ENCODER1_URL}.
      - CMD1=${CMD1} # Optional tuner #1 command; ah4c streams its stdout (end it with "-f mpegts -") instead of fetching ENCODER1_URL. No shell.
      - TUNER2_IP=${TUNER2_IP} # Streaming device #2 with adb port in the form hostname:port or ip:port
      - ENCODER2_URL=${ENCODER2_URL} # Tuner #2 source URL. Streamed directly when CMD2 is blank; otherwise referenced by CMD2 as ${ENCODER2_URL}.
      - CMD2=${CMD2} # Optional tuner #2 command; ah4c streams its stdout instead of fetching ENCODER2_URL. See CMD1.
      - TUNER3_IP=${TUNER3_IP} # Streaming device #3 with adb port in the form hostname:port or ip:port
      - ENCODER3_URL=${ENCODER3_URL} # Tuner #3 source URL. Streamed directly when CMD3 is blank; otherwise referenced by CMD3 as ${ENCODER3_URL}.
      - CMD3=${CMD3} # Optional tuner #3 command; ah4c streams its stdout instead of fetching ENCODER3_URL. See CMD1.
      - TUNER4_IP=${TUNER4_IP} # Streaming device #4 with adb port in the form hostname:port or ip:port
      - ENCODER4_URL=${ENCODER4_URL} # Tuner #4 source URL. Streamed directly when CMD4 is blank; otherwise referenced by CMD4 as ${ENCODER4_URL}.
      - CMD4=${CMD4} # Optional tuner #4 command; ah4c streams its stdout instead of fetching ENCODER4_URL. See CMD1.
      - TUNER5_IP=${TUNER5_IP} # Streaming device #5 with adb port in the form hostname:port or ip:port
      - ENCODER5_URL=${ENCODER5_URL} # Tuner #5 source URL. Streamed directly when CMD5 is blank; otherwise referenced by CMD5 as ${ENCODER5_URL}.
      - CMD5=${CMD5} # Optional tuner #5 command; ah4c streams its stdout instead of fetching ENCODER5_URL. See CMD1.
      - TUNER6_IP=${TUNER6_IP} # Streaming device #6 with adb port in the form hostname:port or ip:port
      - ENCODER6_URL=${ENCODER6_URL} # Tuner #6 source URL. Streamed directly when CMD6 is blank; otherwise referenced by CMD6 as ${ENCODER6_URL}.
      - CMD6=${CMD6} # Optional tuner #6 command; ah4c streams its stdout instead of fetching ENCODER6_URL. See CMD1.
      - TUNER7_IP=${TUNER7_IP} # Streaming device #7 with adb port in the form hostname:port or ip:port
      - ENCODER7_URL=${ENCODER7_URL} # Tuner #7 source URL. Streamed directly when CMD7 is blank; otherwise referenced by CMD7 as ${ENCODER7_URL}.
      - CMD7=${CMD7} # Optional tuner #7 command; ah4c streams its stdout instead of fetching ENCODER7_URL. See CMD1.
      - TUNER8_IP=${TUNER8_IP} # Streaming device #8 with adb port in the form hostname:port or ip:port
      - ENCODER8_URL=${ENCODER8_URL} # Tuner #8 source URL. Streamed directly when CMD8 is blank; otherwise referenced by CMD8 as ${ENCODER8_URL}.
      - CMD8=${CMD8} # Optional tuner #8 command; ah4c streams its stdout instead of fetching ENCODER8_URL. See CMD1.
      - TUNER9_IP=${TUNER9_IP} # Streaming device #9 with adb port in the form hostname:port or ip:port
      - ENCODER9_URL=${ENCODER9_URL} # Tuner #9 source URL. Streamed directly when CMD9 is blank; otherwise referenced by CMD9 as ${ENCODER9_URL}.
      - CMD9=${CMD9} # Optional tuner #9 command; ah4c streams its stdout instead of fetching ENCODER9_URL. See CMD1.
      # ── Streaming app and DVR ───────────────────────────────────────────────
      - STREAMER_APP=${STREAMER_APP} # Streaming device name and streaming app you're using in the form scripts/streamer/app (use lowercase with slashes between as shown)
      - PYATV=${PYATV:-false} # Set to TRUE to run docker-start-pyatv.sh at container start for Apple TV tuners via pyatv, instead of the default docker-start.sh used for adb-based tuners. Case-insensitive; anything else runs the default.
      - CHANNELSIP=${CHANNELSIP} # Hostname or IP address of the Channels DVR server itself
      - FASTCHANNELS_URL=${FASTCHANNELS_URL} # Base URL of your FastChannels container, so the firetv/fastchannels scripts can use its built-in ah4c integration to tune. A URL pre-filled into an exported script overrides this.
      # ── Failure alerts ──────────────────────────────────────────────────────
      # Email via SMTP and/or a webhook GET, sent when a tune fails. Leave blank to disable.
      - ALERT_SMTP_SERVER=${ALERT_SMTP_SERVER} # The domainname:port of the SMTP server you'll be using like smtp.gmail.com:587. This is for sending ah4c alerts if tuning fails.
      - ALERT_AUTH_SERVER=${ALERT_AUTH_SERVER} # The auth server for the e-mail you'll be using like smtp.gmail.com
      - ALERT_EMAIL_FROM=${ALERT_EMAIL_FROM} # The e-mail address you'd like your ah4c failure alert e-mails to show as being from.
      - ALERT_EMAIL_PASS=${ALERT_EMAIL_PASS} # Gmail and Yahoo both support the creation of app-specific e-mail passwords, and this is the way to go! It's NOT recommended to use your everyday e-mail password.
      - ALERT_EMAIL_TO=${ALERT_EMAIL_TO} # The e-mail address you'd like your alert e-mails sent to.
      - ALERT_WEBHOOK_URL=${ALERT_WEBHOOK_URL} # URL to GET when an alert fires (same failures that trigger the e-mail); put $reason in the URL and it's replaced with the URL-encoded message. Blank disables it.
      # ── Guide tuning and content updates ────────────────────────────────────
      - LIVETV_ATTEMPTS=${LIVETV_ATTEMPTS} # For FireTV Live Guide tuning only, set maximum number of attempts at finding the desired channel
      - CREATE_M3US=${CREATE_M3US:-false} # Set to true to create device-specific M3Us for use with Amazon Prime Premium channels -- requires a FireTV device
      - UPDATE_SCRIPTS=${UPDATE_SCRIPTS:-true} # Set to true if you'd like the sample scripts and STREAMER_APP scripts updated whether they exist or not
      - UPDATE_M3US=${UPDATE_M3US:-true} # Set to true if you'd like the sample m3us updated whether they exist or not
      # ── Timezone and session handling ───────────────────────────────────────
      - TZ=${TZ} # Your local timezone in Linux "tz" format
      - SPEED_MODE=${SPEED_MODE:-false} # Set to false if you'd like the target streaming app to be closed after each tuning cycle (limited script support).
      - KEEP_WATCHING=${KEEP_WATCHING} # In supported scripts, set the delay before resending a tuning deeplink to prevent "Are you still watching?" type messages. Examples: Use 4h for 4 hours or 240m for 240 minutes.
      # ── Autocrop (LinkPi encoders only) ─────────────────────────────────────
      - AUTOCROP_CHANNELS=${AUTOCROP_CHANNELS} # Space separated list of channels (by number) with black borders on 4 sides to autocrop while maintaining aspect ratio. Requires LinkPi Encoder!
      - LINKPI_HOSTNAME=${LINKPI_HOSTNAME} # Hostname or IP of the LinkPi Encoder's web API. Required for AUTOCROP_CHANNELS.
      - LINKPI_USERNAME=${LINKPI_USERNAME} # Username for the LinkPi Encoder's web API. Required for AUTOCROP_CHANNELS.
      - LINKPI_PASSWORD=${LINKPI_PASSWORD} # Password for the LinkPi Encoder's web API. Required for AUTOCROP_CHANNELS; not currently read by the bundled scripts, which log in with the LinkPi default password instead.
      # ── Custom startup script ───────────────────────────────────────────────
      - USER_SCRIPT=${USER_SCRIPT} # Path to a custom script to run alongside ah4c at container startup. Blank runs nothing extra.
      # ── Recording continuity and playback timing ────────────────────────────
      # Keep recordings starting on the program and free of gaps within the DVR's 30s tune window.
      - NULL_FRAME_INSERTION=${NULL_FRAME_INSERTION:-false} # Set to TRUE to fill encoder stalls with MPEG-TS NULL packets (PID 0x1FFF) so the DVR never sees a zero-byte gap mid-recording. Case-insensitive (true/True/TRUE all work); anything else, including 1/yes, leaves the feature off.
      - PLAYBACK_DETECTION=${PLAYBACK_DETECTION:-false} # Set to TRUE to hold the stream until the device reports audio playing and the picture moving, then start on a keyframe, so recording begins on the program, not the loading screen. Requires adb; network tuners only. Case-insensitive; anything but true leaves it off.
      - PLAYBACK_STATIC_TIMEOUT=${PLAYBACK_STATIC_TIMEOUT} # Only used with PLAYBACK_DETECTION=TRUE. Seconds the box may keep its prior player/session before the check falls back to gating on motion alone. Default 2 suits Ospreys; apps like DirecTV that hold one player across channel changes need more. 0 or unset uses the default.
      - PLAYBACK_DELAY=${PLAYBACK_DELAY} # Hold every tune this long before handing the DVR the program, so a slow-starting app still records within the DVR's 30s window. Black or a mounted pre-roll fills the wait; the box's own video is never passed through. Accepts a bare number (seconds) or a duration like 30s/1m, capped at 10m. Network tuners only. Empty or 0 disables it.
      - ENCODER_CODEC=${ENCODER_CODEC:-h264} # The video codec your encoder outputs: h264 (default) or h265. Filler black/pre-roll must match it, or playback won't cross to the program at hand-off. Pre-roll video is converted to match; an existing H.265 stream is copied. Leave at h264 unless your encoder is H.265. Case-insensitive; h265/hevc both work.
      - HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-180} # In supported scripts (currently osprey), seconds between keepalive keyevents sent during playback to stop the app's UI inactivity timer from resetting the stream. Default 180; set to 0 to disable.
      # ── NVIDIA GPU ──────────────────────────────────────────────────────────
      # Used by the CUDA caption engine and any CMDn calling h264_nvenc/hevc_nvenc. Needs the NVIDIA container toolkit.
      - NVIDIA_VISIBLE_DEVICES=${NVIDIA_VISIBLE_DEVICES} # For the CUDA caption engine and/or an NVENC CMDn. Set to all alongside DOCKER_RUNTIME=nvidia to expose an NVIDIA GPU. Empty means no GPU and is the default.
      - NVIDIA_DRIVER_CAPABILITIES=${NVIDIA_DRIVER_CAPABILITIES} # Set to compute,utility for the CUDA caption engine. A CMDn using h264_nvenc/hevc_nvenc also needs video - use compute,utility,video (or all).
    volumes:
      - ${HOST_DIR:-/data}/ah4c/config:/opt/config # Settings saved from the web interface
      - ${HOST_DIR:-/data}/ah4c/scripts:/opt/scripts # pre/stop/bmitune.sh scripts will be stored in this bound host directory under streamer/app
      - ${HOST_DIR:-/data}/ah4c/m3u:/opt/m3u # m3u files will be stored here and hosted at http://<hostname or ip>:7654/m3u for use in Channels DVR - Custom Channels settings
      - ${HOST_DIR:-/data}/ah4c/adb:/root/.android # Persistent data directory for adb keys
      - ${HOST_DIR:-/data}/ah4c/captions:/opt/captions # Closed caption settings, and the speech model, engine and any GPU driver downloaded from the Closed Captions page. Stays empty unless you turn captions on
      - ${PREROLL_FILE:-${HOST_DIR:-/data}/ah4c/preroll}:/opt/preroll # A video or still image shown to the DVR instead of NULL packets, during a PLAYBACK_DELAY/PLAYBACK_DETECTION hold or a NULL_FRAME_INSERTION stall. Set PREROLL_FILE to a host path, or drop the file into this directory. Anything ffmpeg reads; prepared once at startup, loops until the real stream is ready
    restart: unless-stopped

If you'd like to have as many options exposed as possible through the new Settings page, then you're going to want to use a more minimal Docker Compose (going this route will require you to set things up through the WebUI that were previously done through env vars).

Here's a recommended Docker Compose for that new minimal route. The Compose itself still requires no editing, but it has a much smaller set of potential env var overrides:

services:
  # 2026.09.16
  # GitHub home for this project with setup instructions: https://github.com/sullrich/ah4c
  # Docker Hub home for this project: https://hub.docker.com/repository/docker/bnhf/ah4c
  ah4c: # This docker-compose typically requires no editing. Use the Environment variables section of Portainer to set your values.
    image: bnhf/ah4c:${TAG:-latest}
    container_name: ${CONTAINER_NAME:-ah4c}
    hostname: ${HOSTNAME:-ah4c}
    dns_search: ${DOMAIN:-localdomain} # Specify the name of your LAN's domain, usually local or localdomain
    runtime: ${DOCKER_RUNTIME:-runc} # Closed captions only. Set DOCKER_RUNTIME=nvidia for an NVIDIA GPU with the CUDA engine build. Requires the NVIDIA container toolkit.
    devices:
      - ${GPU_DEVICE:-/dev/null} # Closed captions only. Set GPU_DEVICE=/dev/dri to let the Vulkan engine build use an Intel or AMD GPU. Left at the default it passes /dev/null, which always exists and does nothing.
    ports:
      - ${HOST_PORT:-7654}:7654 # Port used by this ah4c proxy
    environment:
      - AH4C_COMPOSE=2026.09.16 # Compose file date stamp - do not change. The startup log flags it when out of date.
      - NVIDIA_VISIBLE_DEVICES=${NVIDIA_VISIBLE_DEVICES} # For the CUDA caption engine and/or an NVENC CMDn. Set to all alongside DOCKER_RUNTIME=nvidia to expose an NVIDIA GPU. Empty means no GPU and is the default.
      - NVIDIA_DRIVER_CAPABILITIES=${NVIDIA_DRIVER_CAPABILITIES} # Set to compute,utility for the CUDA caption engine. A CMDn using h264_nvenc/hevc_nvenc also needs video - use compute,utility,video (or all).
    volumes:
      - ${HOST_DIR:-/data}/ah4c/config:/opt/config # Settings saved from the web interface
      - ${HOST_DIR:-/data}/ah4c/scripts:/opt/scripts # pre/stop/bmitune.sh scripts will be stored in this bound host directory under streamer/app
      - ${HOST_DIR:-/data}/ah4c/m3u:/opt/m3u # m3u files will be stored here and hosted at http://<hostname or ip>:7654/m3u for use in Channels DVR - Custom Channels settings
      - ${HOST_DIR:-/data}/ah4c/adb:/root/.android # Persistent data directory for adb keys
      - ${HOST_DIR:-/data}/ah4c/captions:/opt/captions # Closed caption settings, and the speech model, engine and any GPU driver downloaded from the Closed Captions page. Stays empty unless you turn captions on
      - ${HOST_DIR:-/data}/ah4c/preroll:/opt/preroll # A video or still image shown to the DVR instead of NULL packets, during a PLAYBACK_DELAY/PLAYBACK_DETECTION hold or a NULL_FRAME_INSERTION stall. Set PREROLL_FILE to a host path, or drop the file into this directory. Anything ffmpeg reads; prepared once at startup, loops until the real stream is ready
    restart: unless-stopped

And, here's a sample set of overrides for the minimal Compose:

TAG=beta
GPU_DEVICE=
HOST_DIR=/data

Here's what the new Settings page looks like:

Many thanks to @mackid1993, and I'm sure he'll add a post expanding on all the new features available through this beta.

2 Likes

Thank you @bnhf!

I think my favorite feature I added is the ability to pull scripts on demand directly from GitHub without waiting for a container rebuild. So if someone merges a PR for a new script or set of scripts you can query GitHub right away for that script and install it on your server rather than waiting for a Docker rebuild, which is easier for @bnhf and also better for the end user. It's also gated specifically to sullrich/ah4c do you know you're getting clean scripts you're not getting anything that is going to harm your server or anything like that. This means either sullrich or bnhf reviewed the scripts, and they're ready to go into production.

I also added an option to push an M3U file right into Channels DVR directly from ah4c without having to copy and paste the URL manually. Kind of like how FastChannels does it. You can also upload M3U files now, which is much easier than before, where you had to connect to a server and copy it over, maybe over SCP or over SFTP. Pre-roll is also handled through a browser upload function, which is much nicer and can be deleted through a browser upload function.

Same thing for custom scripts. You have to have the package of three, but you can set the path, like the streaming device and streaming app, right in the GUI and upload the three script files. And it creates the folders for you and chmods them and puts them in the scripts directory. And you can then choose it from the list.

Something I need an Apple TV user to graciously test if possible, I added a pairing interface for pyatv. When you get the code on your Apple TV, you just pop it right into the screen in ah4c. This is supposed to reduce friction, hopefully, but I just need to know that it definitely works. I have not been able to test it myself. In theory, it should work because I wrote tests to hopefully exercise it as much as I can. But having a real world example of it working would be nice to know. If it does not work, I would be happy to debug with whoever decides to test it and obviously push a test container myself to make sure it's working to not leave anyone stranded.

Lastly, there's a wizard for initial setup that guides you through setting your IP addresses, like for Channels DVR and the proxy address, along with some helpful pointers to make it easier for new folks. When you choose to not manage tuners through the environment variables, you set them completely in the GUI, which is really nice. And you can also check connectivity with the click of a button, which I prefer because it lets you know that it's connected. It also runs adb connect on Android devices, so you'll get that prompt to authorize them right away.

One piece of feedback I can use should we have an easily accessible button to rerun the wizard?

Currently, if you type http://MY_IP/7654/settings?wizard=1 i'll take you back to the wizard right away, but I can easily just program a little button to do that. I don't know if people find it useful enough for that to be a persistent option, or if it's just good one time. The reasoning is with the end of the cable card coming, I want to position AH4C to become a really easy-to-use tool for people who might not be as tech-savvy. So that was why I decided to design a first-time setup wizard to kind of get them acquainted with how HDMI encoders work and just the basics of setting the tool up.

Personally, I think managing it through the interface in settings is much easier than the environment variables, so I would definitely encourage everyone to try. The worst part of it is just switching your Docker Compose out and just plugging in your tuner and encoder addresses again. You don't really need to reauthorize them because that should all be saved in the bind mounts. So they'll just connect right away once you plug them in. It has to populate a settings.json file stored in /opt/config. Rather than setting your environment variables, you just tweak your settings right in the user interface. It will let you know if you have to restart the container, which it will easily do for you in the user interface. You don't have to go back out into your Docker manager and restart it. It'll automatically reload when the container is back up, which is super nice. Lastly, if there are active tunes, it will warn you before you restart the container, so you can defer or choose to restart immediately. I also added the option to easily graphically reorder tuners and encoders. If anyone wants the way that's done tweaked, please let me know. I initially tried drag and drop, but it was just way too wonky and seemed hard if you had a lot of encoders, so I just changed it to a numbered setup where no two numbers can collide.

The other thing is if you have the variable NUMBER_TUNERS set in any way, even set to zero, the container assumes that your tuners are managed through environment variables and just blocks out managing tuners through the interface. So don't set that. But if you don't have that set and start setting tuners through the environment variables, they're ignored. Something has to gate it, and I decided that was the best way to gate it off. If you're doing it through the interface, they're manually counted by the logic of the software, so there's really nothing else to do other than just input your number of tuners and it'll handle populating that. Initially, tried a mixed setup where you can maybe have some tuners manage through environment variables and some not, but that's just illogical, I felt like, and just going to lead to race conditions and issues.

You can, however, set things through environment variables for the most part and set certain things through the interface depending on what you want blocked off and what you want to control graphically. I would think most people would want to control most things in the GUI because it's so much easier. And that settings.json you can easily back up and restore. It's just stored in a bind mount on your server. I think it makes it a lot easier, especially setting up tuners and encoders. You can clearly see which tuner maps to which encoder, maps to which CMD command, etc.

Please do provide feedback, let me know what you think, and I can always expand on this, tweak things, add more things, whatever people like, I will do. I just want this to be the most flexible option for running Channels with HDMI encoders out there. This is also the beauty of free open source software. Anyone can kind of contribute, and we can kind of get a whole community rallied around something, and I love that personally.

2 Likes

so I've about had it with docker for windows. same old problems with reboots not starting portainer. an now tonight after windows update and docker update I'm getting this

failed to deploy a stack: compose up operation failed: Error response from daemon: ports are not available: exposing port TCP 0.0.0.0:7654 -> 127.0.0.1:0: /forwards/expose returned unexpected status: 500

I got that on my previously working stack, new stack, beta stack.. no ah4c for me

@Shaggylive I had a similar issue this morning with my Immich server port. Follow these steps and I bet it will fix it (use your port number not mine from the example)

Run this in an elevated PowerShell prompt on win11pc:
"PowerShell

netsh interface ipv4 show excludedportrange protocol=tcp"

Verification: Look through the start/end ranges. If 2283 falls inside any listed range, Windows has locked that port for system/hypervisor services.

  1. Release the Port Block ImmediatelyIn your Administrator PowerShell window, run:
"PowerShell

net stop winnat"

Run the check again to verify the block cleared:

"PowerShell

netsh interface ipv4 show excludedportrange protocol=tcp"

Permanent Fix: Lock 2283 Out of Dynamic Exclusions
Windows recalculates these dynamic pools on reboot, so port 2283 could get swallowed again down the road. You can prevent Windows from ever claiming it by adding an administered exclusion for Immich:

  ```"PowerShell

net stop winnat
netsh int ipv4 add excludedportrange protocol=tcp startport=2283 numberofports=1
net start winnat"
then deploy the stack in Portainer.

This was the first time I had ever seen this happen myself.

Thanks, I knew that update affected domains, but silly me thinking i'd have no problems.. lol

anyway before I fell alseep last night I had uninstalled docker and reinstalled with CDVR WinUtil, and installed the ezstart.
saw your post this am. the port was not in use, so I created a beta stack successfully!

@mackid1993 beta's working good so far, one thing I noticed since I nuked my setup is that i was unable to view/control the new tuners to authorize until I rebooted the stack.
also one of my recording locked a tuner before I could turn on CC, so I had to release it a couple times for cdvr to give up and let me turn CC on. however the tuner was still showing in use (in ah4c) even though cdvr was not recording it. I had to restart the stack again.
I could use a button for that in GUI, instead I had to change a setting and save to get a restart stack prompt.

ALSO, THANK YOU FOR FINDING A WAY TO UPLOAD M3U, terminal was a pain.!!!!!! :+1:

bnhf/ah4c:beta2 pushed this morning, with minor fixes and polish.