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

With the help of my new best friend, Claude, I was able to figure out the following regarding the Spectrum app for Android TV:

I was able to launch the application using:

adb shell am start -n com.spectrum.stva.androidtv/.ui.HostedMainActivity

After some tinkering, Claude determined that "there are no deeplink intent filters at all — no custom scheme, no HTTPS app links. This tells us the app handles navigation internally after launch, not via external URIs," and, "Since it's a WebView app, channel navigation almost certainly happens via JavaScript/web routing inside the WebView, not Android intents. The URI you pass gets handed to the web layer, which decides what to render."

After a few more tests, we determined that the app will tune using channel numbers. After launching the app, we were able to use ADB to tune like this:

# KEYCODE map for digits
# 0=7, 1=8, 2=9, 3=10, 4=11, 5=12, 6=13, 7=14, 8=15, 9=16

# Example: Tune to channel 20 (press 2, then 0)
adb shell input keyevent 9   # press "2"
adb shell input keyevent 7   # press "0"`

We also tried the following, but the app only received the last digit of the sent text:

# Channel 35
adb shell input text "35"`

Long story short, we created a script that works reliably using ADBTuner:

JSON{
    "name": "Spectrum TV",
    "author": "Custom",
    "version": "2.1",
    "description": "Tunes Spectrum TV via channel number keyevents. Set TARGET_URL_OR_IDENTIFIER to the channel number (e.g. 35 for CNN). Spectrum uses an internal WebView router with no deeplink support — channels are tuned via keyevents after app launch.",
    "uuid": "f4a7b2c9-d831-4e56-9f0a-7c3e8b1d2a45",
    "global_options": {
        "wait_for_video_playback_detection": true,
        "use_fixed_delay": false,
        "fixed_delay_seconds": 0,
        "wait_after_post_playback_start_commands_seconds": 0
    },
    "pre_tune_commands": [
        "input keyevent KEYCODE_MEDIA_STOP",
        "am force-stop com.spectrum.stva.androidtv",
        "input keyevent KEYCODE_HOME"
    ],
    "tune_commands": [
        "adbtuner_open_app com.spectrum.stva.androidtv",
        "sleep 8",
        "input keyevent KEYCODE_DPAD_CENTER",
        "for digit in $(echo '||TARGET_URL_OR_IDENTIFIER||' | grep -o '.'); do input keyevent KEYCODE_$digit; sleep 0.4; done",
        "sleep 1"
    ],
    "tune_match_text_commands": [
        {
            "match_text": [
                "who's watching",
                "choose an account",
                "edit profiles",
                "sign in"
            ],
            "commands": [
                "input keyevent KEYCODE_DPAD_CENTER"
            ],
            "start_checking_after_seconds": 3
        }
    ],
    "post_playback_start_commands": [],
    "post_tune_commands": [
        "input keyevent KEYCODE_MEDIA_STOP",
        "am force-stop com.spectrum.stva.androidtv",
        "input keyevent KEYCODE_HOME"
    ],
    "timed_keep_active_commands": []
}

It is slow, but it works.

I hope this somehow helps the community.

1 Like

While very cool this is an adbtuner config, the logic would have to be adapted to ah4c bash.

Agreed! Just wanted to put this out there as a reference.

1 Like

Yeah, Claude could easily convert this into a proper ah4c scripts if given a reference. Cool stuff! Also, just so you're aware, ah4c excels at tuning by channel number. This might actually be a better project for what you're trying to do.

Thanks, got it. Is there a best practice for whether or not to sleep the device as part of that cycle? Currently watching a channel and then going back to the Guide and choosing a different channel results in a full sleep-wake cycle which makes tuning the new channel quite slow. I feel like it would be nice to be able to have a delay, and only sleep the device after 5 minutes of no watching activity for example. That would allow more rapid tuning when changing between channels via the Guide.

Also I'm seeing that switching directly from one channel to another in Channels (without going back to the Guide) results in a "tuner in use" error because ah4c sees the new channel as a new tuning session, not a new tuning for the existing session.

1 Like

the priority is for recordings. changing the existing tune would make reliable recordings impossible.

yes, at least 2 tuners is highly recommended.

1 Like

I guess my eight tuners should be enough. LOL

Yeah...1 tuner won't cut it for channel surfing. If you're using a LinkPi @Lambers, there's an USB input which you can use as an extra tuner by getting an HDMI USB grabber

No -- that's definitely your choice. Some devices need to sleep or they're streaming 24x7, which isn't necessarily great if you have 5+ devices. :slight_smile:

It's not typical for this type of virtual tuning to be speedy. 3-5 seconds is rarefied air, 8-10 seconds more average, with 15 seconds needed for some scenarios.

This should be possible to script.

You definitely want multiple tuners with all of these virtual tuner solutions. Plus as mentioned by others, channel surfing can be done -- but don't expect a cable or satellite box type experience. DTV with Osprey boxes, or YTTV with the newest FireTV Cubes would get you close -- but otherwise you're looking 5-6 seconds minimum for typical virtual tuning.

1 Like

What is the best set of scripts to use with Onn boxes and DirecTV? For now, I took the Osprey deep link scripts and modified them slightly, and they're working. I'm just curious if there's something that's already pre-configured that I don't have to modify.

The Fire TV scripts look pretty specific to Amazon's config. Since the modified Osprey scripts are working for me, I can always PR them back as like a Google TV variant if anyone else is willing to test what I came up with. It was just a URL scheme change, a slight change to the ADB command, and changing the package name.

I am using the following after generating the directv m3u with olive tin. Some of it of course doesn't apply given the capture device you are using. Excuse the redundant commands where you see them. I just have not bothered to clean up.

bimtune.sh:

#!/bin/bash

callSign=$(echo $1 | awk -F~ '{print $1}')
contentID=$(echo $1 | awk -F~ '{print $2}')
tunerIP="$2"

# Map tuner IP to Magewell input
case "$tunerIP" in
    "192.168.1.130") streamPath="stream/0-1" ;;
    "192.168.1.131") streamPath="stream/0-2" ;;
    "192.168.1.132") streamPath="stream/0-3" ;;
    "192.168.1.133") streamPath="stream/0-4" ;;
    *) streamPath="stream/0-1" ;;
esac

encoderURL="http://192.168.1.188:6502/$streamPath"

adb -s $tunerIP shell am start -S -W \
  -a android.intent.action.VIEW \
  -d "dtvnow://deeplink.directvnow.com/play/channel/$callSign/$contentID"

# Wait until Magewell is receiving signal 
sleep 8

prebmitune.sh:

#!/bin/bash

TUNERIP="$1"

adb connect $TUNERIP

 #Check screen state
SCREEN=$(adb -s $TUNERIP shell dumpsys power | grep 'mWakefulness=' | awk -F= '{print $2}' | tr -d '[:space:]')
if [ "$SCREEN" != "Awake" ]; then
    adb -s $TUNERIP shell input keyevent KEYCODE_WAKEUP
    sleep 2
fi

adb -s $TUNERIP shell input keyevent KEYCODE_WAKEUP
adb -s $TUNERIP shell input keyevent KEYCODE_WAKEUP
adb -s $TUNERIP shell input keyevent KEYCODE_WAKEUP

stopbmitune.sh:

#!/bin/bash

TUNERIP="$1"

adb -s $TUNERIP shell "input keyevent KEYCODE_MEDIA_STOP"
adb -s $TUNERIP shell "input keyevent KEYCODE_MEDIA_STOP"
adb -s $TUNERIP shell "input keyevent KEYCODE_HOME"
adb -s $TUNERIP shell "input keyevent KEYCODE_HOME"

I'm wondering what the best way to run more than nine tuners is?

Is it best to split them across two stacks? Is there a way to run ten tuners inside ah4c? It looks like there's a cap of nine.

too much winning i guess.. lol
I have a 2x2 setup. identical except for the lower frame rate on my usb's.
I favorite movie and other 4k channels with the 2xHdmi set and I favorite the news and low res retroTv to the lower framerate 2xusb

I've actually been considering splitting them all out individually, but I bet it'd be a network disaster.

1 Like

I just set CDVR to dedupe the guide based on guide data and set the same channel numbers that way it can just fall thru tuner priority naturally.

I've been learning what that nature actually is recently.
groups in order Favorites, HD, SD, none
each group looks by source priority in reverse channel # order.

I'm talking about tuner priority and this setting:

My thinking is it'll let me use two stacks and kind of just let them fall over to the next one.

Yes definitely, and it makes sense with LinkPi's 5 tuners per encoder. I'll add that soon-ish.

Oh, that would be best if I could add another tuner.
My whole thing is I have two ENC5s, or at least one on the way, so I wanted to run 10 Ospreys, and I realized it was maxing out at nine.

If you need help drafting a PR, let me know if I can be of any assistance. I have a long weekend right now!

@bnhf

I got this working, minimal diff too.

Test build at ghcr.io/mackid1993/ah4c:latest.

1 Like

Merged. Nicely done. I'll build a new container in the next day or so.

2 Likes