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

Is there a FAQ for this? Is it possible to get the ESPN+ events through this container?

ELI5 for a novice, please.

Both ah4c and ADBTuner are projects designed to capture the output from an app running on an Android-based streaming stick using an encoder. That part is relatively easy; what's trickier is being able to do virtual channel changing in that app.

Apps that have deeplinks (not always easy to figure out), make the best candidates, but ah4c also supports remote control emulation (trickier to make reliable) to do that virtual channel changing. I don't believe the ESPN app is a very good candidate for either.

1 Like

Hey quick question. Before i dive into ah4c i see nba league pass is now on prime video. Is prime video good for tuning to specific games with ah4c? I already had adb tuner working its taken away from youtube tv.

How do I get started?

@arwrite So it sounds like you have someone willing/able to guide you, if you choose to install ah4c directly on your Linux host. If you decide to go this route, and you have the option, you may want to set up a test server to get this going, rather than using your production CDVR server.

Not a requirement obviously, but that's what I'd do, as things can get a bit messy when you're trying to get something like this going directly on the host OS. One of the beauties of containerization (and virtualization in general), is that you can always blow away a container or VM and start over with a clean slate.

When I'm doing something experimental, I'll almost always do a fresh installation once I figure out the steps, both to confirm those steps are correct, and to get rid of any unneeded installations or tweaks I did along the way.

Do you have any idea if the Prime app supports deeplinks? If not, you'd need to determine if you think the way games are presented in the app would lend itself to a remote control emulation approach to tuning.

Back when I was using remote control emulation with DTV, I would navigate to the search box, and then use a search that would match one (and only one channel) to do the tuning. This worked pretty well, but was not super quick. Deeplinks are always the way to go when available, but figuring them out can be tricky.

Either way, you'll need to do some light Bash scripting to make it all happen.

1 Like

Got it. This makes a lot of sense

Like I said, I don't think ESPN/ESPN+ is a good candidate, but if you want you mess around with it anyway you could set things up as though you were using DTV. You could use a STREAMER_APP value of scripts/firetv/dtvdeeplinks and a CDVR_M3U_NAME of dtvdeeplinks.m3u.

This would give you an idea of what the scripts would look like, and an M3U. You can find the Docker Compose, and some example env vars here:

2 posts were merged into an existing topic: ADBTuner: A "channel tuning" application for networked Google TV / Android TV devices

MovieSphere Gold got recently added to DTV/Stream. For those who need the streamlink, its

http://{{ .IPADDRESS }}/play/tuner/MVSGLD~6b272ceb-e01c-4c9a-b464-9e169f766fdb

and gracenote id is 185303

2 Likes

Thank you! MovieSphere Gold also has been added to FRNDLY's lineup.

@bnhf I've been trying out the pyatv version of ah4c for the last few days, and am trying to figure out if I can delay tuning while prebmitune and bmitune run so the stream seems more seamless (not showing app UI elements and button presses). Is there a way to run the above ffmpeg command simultaneous to prebmitune and bmitune? Right now, it seems like the tuning scripts run after CMD1.

I don't normally use this, but I just tested it a couple of times -- and it's working as intended for me.

For a definitive test, I set the -ss value to 20, which is also the number of seconds I wait before jumping to "live" in the current testing I'm doing with the ESPN app. Sure enough, I saw nothing of the tuning process, but when the video started it jumped to live immediately.

Your Docker Compose should look something like this:

services:
  ah4c: # This docker-compose typically requires no editing. Use the Environment variables section of Portainer to set your values.
    # 2025.09.13
    # GitHub home for this project: https://github.com/bnhf/ah4c.
    # Docker container home for this project with setup instructions: https://hub.docker.com/r/bnhf/ah4c.
    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
    #devices:
      #- /dev/dri:/dev/dri # Uncomment for Intel Quick Sync (GPU) access
    ports:
      - ${ADBS_PORT:-5037}:5037 # Port used by adb-server
      - ${HOST_PORT:-7654}:7654 # Port used by this ah4c proxy
      - ${SCRC_PORT:-7655}:8000 # Port used by ws-scrcpy
    environment:
      - IPADDRESS=${IPADDRESS} # Hostname or IP address of this ah4c extension to be used in M3U file (also add port number if not in M3U)
      - NUMBER_TUNERS=${NUMBER_TUNERS} # Number of tuners you'd like defined 1, 2, 3 or 4 supported
      - TUNER1_IP=${TUNER1_IP} # Streaming device #1 with adb port in the form hostname:port or ip:port
      - TUNER2_IP=${TUNER2_IP} # Streaming device #2 with adb port in the form hostname:port or ip:port
      - TUNER3_IP=${TUNER3_IP} # Streaming device #3 with adb port in the form hostname:port or ip:port
      - TUNER4_IP=${TUNER4_IP} # Streaming device #4 with adb port in the form hostname:port or ip:port
      - TUNER5_IP=${TUNER5_IP} # Streaming device #5 with adb port in the form hostname:port or ip:port
      - TUNER6_IP=${TUNER6_IP} # Streaming device #6 with adb port in the form hostname:port or ip:port
      - TUNER7_IP=${TUNER7_IP} # Streaming device #7 with adb port in the form hostname:port or ip:port
      - TUNER8_IP=${TUNER8_IP} # Streaming device #8 with adb port in the form hostname:port or ip:port
      - TUNER9_IP=${TUNER9_IP} # Streaming device #9 with adb port in the form hostname:port or ip:port
      - ENCODER1_URL=${ENCODER1_URL} # Full URL for tuner #1 in the form http://hostname/stream or http://ip/stream
      - ENCODER2_URL=${ENCODER2_URL} # Full URL for tuner #2 in the form http://hostname/stream or http://ip/stream
      - ENCODER3_URL=${ENCODER3_URL} # Full URL for tuner #3 in the form http://hostname/stream or http://ip/stream
      - ENCODER4_URL=${ENCODER4_URL} # Full URL for tuner #4 in the form http://hostname/stream or http://ip/stream
      - ENCODER5_URL=${ENCODER5_URL} # Full URL for tuner #5 in the form http://hostname/stream or http://ip/stream
      - ENCODER6_URL=${ENCODER6_URL} # Full URL for tuner #6 in the form http://hostname/stream or http://ip/stream
      - ENCODER7_URL=${ENCODER7_URL} # Full URL for tuner #7 in the form http://hostname/stream or http://ip/stream
      - ENCODER8_URL=${ENCODER8_URL} # Full URL for tuner #8 in the form http://hostname/stream or http://ip/stream
      - ENCODER9_URL=${ENCODER9_URL} # Full URL for tuner #9 in the form http://hostname/stream or http://ip/stream
      - CMD1=${CMD1} # Typically used for ffmpeg processing of a device's stream. ffmpeg -i ${ENCODER1_URL} -ss 8 -c:v copy -c:a copy -f mpegts -
      - CMD2=${CMD2} # Typically used for ffmpeg processing of a device's stream. ffmpeg -i ${ENCODER2_URL} -ss 8 -c:v copy -c:a copy -f mpegts -
      - 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)
      - CHANNELSIP=${CHANNELSIP} # Hostname or IP address of the Channels DVR server itself
      - 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=""
      - 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 of not
      - UPDATE_M3US=${UPDATE_M3US:-true} # Set to true if you'd like the sample m3us updated whether they exist of not
      - 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:-235m} # 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_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 address of your LinkPi encoder. For use with AUTOCROP_CHANNELS.
      - LINKPI_USERNAME=${LINKPI_USERNAME} # LinkPi username. For use with AUTOCROP_CHANNELS.
      - LINKPI_PASSWORD=${LINKPI_PASSWORD} # LinkPi password. For use with AUTOCROP_CHANNELS.
      - USER_SCRIPT=${USER_SCRIPT} # A custom user script to be run at container start. If placed in /HOST_DIR/olivetin, only the script name is required, like ./userscript.sh
    volumes:
      - ${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
    restart: unless-stopped

And in the Environment variables section of the Portainer-Stacks Editor:

TAG=latest
CONTAINER_NAME=ah4c
HOSTNAME=ah4c
DOMAIN=localdomain tailxxxxx.ts.net
ADBS_PORT=5037
HOST_PORT=7654
SCRC_PORT=7655
IPADDRESS=htpc6:7654
NUMBER_TUNERS=2
TUNER1_IP=firestick-rack1:5555
ENCODER1_URL=http://encoder_48007/0.ts
CMD1=ffmpeg -i ${ENCODER1_URL} -ss 20 -c:v copy -c:a copy -f mpegts -
TUNER2_IP=firestick-rack2:5555
ENCODER2_URL=http://encoder_48007/4.ts
CMD2=ffmpeg -i ${ENCODER2_URL} -ss 20 -c:v copy -c:a copy -f mpegts -
STREAMER_APP=scripts/firetv/espn
CHANNELSIP=media-server8
ALERT_SMTP_SERVER=smtp.gmail.com:587
ALERT_AUTH_SERVER=smtp.gmail.com
ALERT_EMAIL_FROM=[Redacted]
ALERT_EMAIL_PASS=[Redacted]
ALERT_EMAIL_TO=[Redacted]
LIVETV_ATTEMPTS=
CREATE_M3US=false
UPDATE_SCRIPTS=true
UPDATE_M3US=true
TZ=US/Mountain
SPEED_MODE=false
KEEP_WATCHING=235m
AUTOCROP_CHANNELS=
LINKPI_HOSTNAME=
LINKPI_USERNAME=
LINKPI_PASSWORD=
USER_SCRIPT=
HOST_DIR=/data
CDVR_M3U_NAME=espn_plus.m3u

Note that the env vars used in CMD1 and CMD2 are not placeholders -- those variable names should be used as shown. Adjust the number of seconds after -ss to suit.

Good call on testing with a much longer delay, CMD1 is indeed working as expected and I probably wasn't accounting for some inconsistent lag from my Apple TV responding to the tuning scripts. I'm not sure if it was using your slightly updated compose but everything seems to be a little snappier now. Thank you for taking some time to check it out for me.

@bnhf i see there is a Kodi favourites script on github but wanted to ask if it would be possible to tune channels on a Kodi box with JSON?

curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"Player.Open","params":{"item":{"channelid":**14**}}}' http://localhost:8080/jsonrpc

Example above to tune to channel 14

I was not very involved in those particular scripts. I'd suggest searching this thread for detail on what the person that wrote them was trying to accomplish.

Thanks but i dont want to use the Kodi script. What im really asking if its possible to adapt your basic script and tune channels with the curl command above and get ch no from m3u?

When Channels DVR wants to tune using ah4c, it passes two values to the ah4c scripts. The hostname or IP of the encoder-connected streaming stick to use (with port), and the value after the last slash in the M3U's URL.

That value after the last slash can be anything you want it to be, but it's typically something like a channel number or other unique identifier that allows for the channels to be tuned.

So in your example, if the part of the curl command that changes is the value currently represented by "14", then you'd use that after the last slash in the M3U's URL for that station "record".

You can even pass multiple values if needed. I usually separate multiple values with a tilde (~), and then parse them in the script. Like if you needed to pass a channel name and a deeplink, you could do that.

It's really only limited by your imagination. :slight_smile:

2 Likes