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

I mean, if it's up to me, the closed caption work is basically done.
As long as no one else runs into some weird edge case, I can't see why we need to make changes to it.

At this point, I think this has become a bit of a refactor, and what you just opened as a question might be worth exploring before we merge this branch.

Cool. I can see a really slick common ui, eventually you can have a mix of adb and atv tuners in the tuner status page etc.

Eventually we can backfill the PYATV versions of missing pieces in the UI like the remote emulation buttons, etc. perhaps even ATV pairing.

I agree. My preference at this point would be a brief pause in changes, and if no "must fix" items pop-up, pull PR#20 in. This is a big PR, but once we get it merged, we should be able to return to some more manageable PR sizes.

There will always be new features that can be added or improved upon, but for now this seems to me to be a very nice upgrade, with plenty of changes for people to adsorb and utilize.

You've done an amazing job on this, and have really given a major face and functionality lift to this project!

2 Likes

Thank so much and I agree!
I think the next step going forward would be to break main.go up into more maintainable pieces so we can better extend things in the future. Personally I'd like to see less env vars and more UI based configuration. I think we can make this tool really user friendly if we allowed for more config through the new UI.

1 Like

Makes sense.

I'd be fine with that, as long as we continue to support env vars as well.

None of this should be part of PR#20 though! Bug fixes only to that PR please, so we can get it merged soon. The changes I've made to the beta branch on top of PR#20 should be easy to replicate, so I think rather than creating a PR from the beta branch, I'll delete it and merge your PR. Once that's done, I'll add my changes back -- which are script, yaml and env related.

Just mentioning something I was playing with this morning.

Channels DVR doesnt display closed captioning in Live multi view.

So this morning using the PYATV commands, I was able to create a toggle for each tuners broadcast closed captioning for use in live situations.

I implemented a Docker container running a lightweight Python HTTP webhook service, and leveraged the Apple TV accessibility shortcut set to closed captions. three quick menu presses toggles the built in CC

Then I created a couple of apple shortcuts use get contents of URL , POST for each tuner.

Ive got it working with an Apple Watch complication right now.

In my tuner case this is very stable, because the ATV app I use adheres to the apple system level CC control and never changes. Some other apps - like Netflix dont.

Im getting ready for all my live view sports that are starting in a few weeks!!!

For the main apple tv that i use to view channels on my couch , i leverage channels api to toggle cc

So we have the best of both worlds. Woohoo!

What are the resource requirements for adding CC to ah4c?

1 Like

Depends on the model chosen and the number of tuners running at once. I tried to make it work with pretty much everything but a literal potato. What do you run ah4c on?

Well plenty of tools allow an environment variable to override an in app config setting. I think it's doable.

Synology running (3) Ospreys total - most times 1 or 2

If it needs folders, I’ll have to create before.

Need to be more specific what kind of cpu how much ram? I doubt it has an iGPU so you prob want the small model Parakeet TDT-CTC 110M.

CPU: 2 cores / Intel(R) Celeron(R) J4025 CPU @ 2.00GHz
4GB Ram
Synology DS220+

On the plus side, it does have an iGPU. :slight_smile:

That's really awesome if it does have an iGPU that should handle maybe one or two streams.

In ah4c, FireTV with LinkPi encoder, everything is working fine with a sling.m3u lineup. Can I add a channel from an app outside of the Sling app? I need to launch the Tennis Channel app, click Down twice and hit Enter, for a manual launch of the Live feed. I wish there was a deeplink for that but I have no idea how to find it. Any help greatly appreciated, thanks.

If you have the Tennis channel app installed on your firestick you can query from the docker console using adb commands and find out the package to launch, and there are definitely adb commands to do the button presses.

If you add the tennis channel in your m3u you can then add a case statement in your bmitune.sh to check for that channel and hardcode the adb command sequence in the tuning script. Then you may have add something to do housekeeping for that channel in stopbmitune.sh

Thats one way to get it done. .

Uncle CLAUDE can walk you through this process.

If you get stuck i can lend a hand later

1 Like

There's a new way to do this, that I just tested. Here are the basics:

Use a STREAMER_APP= value of scripts/all/all

Create an M3U called all.m3u with whatever virtual channels you want. In your case, you'd start with the contents of sling.m3u, and add everything to all.m3u. For each tuning URL in all.m3u, you'll be adding the device and provider to that URL, in the form:

http://{{ .IPADDRESS }}/play/tuner/firetv~dtvstreamdeeplinks~KWGN~6d8950b7-cdb0-8ce8-7b86-1fb8a938cbbe

In other words, after the tuner/ part, add <device>~<provider~, just as they appear in your current scripts/<device>/<provider> value for STREAMER_APP.

Get it? :slight_smile:

The all/all scripts are dispatchers, and will use the scripts from <device>/<provider> to do the actual tuning. Via this approach, ah4c is no longer limited to a single provider.

Try this first for Sling only, and then you can create scripts for the Tennis Channel, along with adding an M3U record for that channel.

1 Like

This is something I want an UI for.

This is the perfect entry point for a UI where we can have different streamer apps.

After I take a little break after that monster PR, maybe I'll start working on something like that.

Since we have a library of scripts, we really could write a graphical script library where you can just choose your script and maybe even have some sort of callback to project one click if it's installed and detected.

I'd actually love to see some integration with Project OneClick for building M3Us, and just make this really super easy for people. Just one click and you get all your channels in an M3U. Kind of like what we already have, but fully automated. Just a thought.

2 Likes

Slick ideas!

Btw i did notice another possible artifact of this merge. It might have happened during the first pull though, a bunch of new M3Us got populated on top of my existing m3u directory. It didnt wipe out my custom one.

I have the environment variables to pull and update scripts and m3us set to false

1 Like

Thanks for all the ideas and help. I ended up mixing both sling and the Tennis Channel app in the same m3u using a random hex token. Then used if/else logic in bmitune.sh. Here's the script in case it helps anybody:

#!/bin/bash
#bmitune.sh for firetv/sling

set -x

channelID="$1"
specialID="$1"
streamerIP="$2"
streamerNoPort="${streamerIP%%:*}"
adbTarget="adb -s $streamerIP"
packageName=com.sling

if [ "$channelID" == "fc65b6abb023efabfaea08e47fe89beb" ]; then
  packageName=com.tennischannel.tceverywhere.amazon
fi

finish() {
  echo "bmitune.sh is exiting for $streamerIP with exit code $?"
}
trap finish EXIT

updateReferenceFiles() {
  mkdir -p $streamerNoPort
  [[ -f "$streamerNoPort/stream_stopped" ]] || echo 0 > "$streamerNoPort/stream_stopped"
  [[ -f "$streamerNoPort/last_channel" ]] || echo 0 > "$streamerNoPort/last_channel"
  echo $$ > "$streamerNoPort/bmitune_pid"
  echo "Current PID for this script is $$"
}

matchEncoderURL() {
  case "$streamerIP" in
    "$TUNER1_IP") encoderURL=$ENCODER1_URL ;;
    "$TUNER2_IP") encoderURL=$ENCODER2_URL ;;
    "$TUNER3_IP") encoderURL=$ENCODER3_URL ;;
    "$TUNER4_IP") encoderURL=$ENCODER4_URL ;;
    *) exit 1 ;;
  esac
}

activeAudioCheck() {
  local startTime=$(date +%s)
  local maxDuration=60
  local minimumLoudness=-50
  local sleepDuration=0.5

  while true; do
    checkLoudness=$(ffmpeg -t 1 -i $encoderURL -filter:a ebur128 -map 0:a -f null -hide_banner - 2>&1 | awk '/I:        /{print $2}')

    if (( $(date +%s) - $startTime > $maxDuration )); then
      echo "Active audio stream not detected in $maxDuration seconds."
      exit 1
    fi

    if (( $(echo "$checkLoudness > $minimumLoudness" | bc -l) )); then
      echo "Active audio stream detected with $checkLoudness LUF."
      break
    fi

    if appFocusCheck 0; then
      echo "Active audio stream not yet detected -- loudness is $checkLoudness LUF. Continuing..."
      sleep $sleepDuration
    else
      echo "No active audio stream detected and app is not in focus after $(($(date +%s) - $startTime)) seconds -- attempting to tune again..."
      tuneChannel
    fi
  done
}

appFocusCheck() {
  if [ "$channelID" == "fc65b6abb023efabfaea08e47fe89beb" ]; then
    return 0   # trust the audio-loudness check alone for this app; skip focus-based retune
  fi
  appFocus=$($adbTarget shell dumpsys window windows | grep -E 'mCurrentFocus' | cut -d '/' -f1 | sed 's/.* //g')
  if [[ $appFocus == $packageName ]]; then
    return 0
  else
    return 1
  fi
}

specialChannels() {
    if [ $specialID = "exit" ]; then
      echo "Exit $packageName requested on $streamerIP"
      rm $streamerNoPort/last_channel $streamerNoPort/adbAppRunning
      $adbTarget shell am force-stop $packageName
      exit 0
    elif [ $specialID = "reboot" ]; then
      echo "Reboot $streamerIP requested"
      rm $streamerNoPort/last_channel $streamerNoPort/adbAppRunning
      $adbTarget reboot
      exit 0
    elif [[ -f $streamerNoPort/adbCommunicationFail ]]; then
      rm $streamerNoPort/adbCommunicationFail
      exit 1
    else
      echo "Not a special channel (exit nor reboot)"
    fi
}

tuneChannel() {
  if [ "$channelID" == "fc65b6abb023efabfaea08e47fe89beb" ]; then
    $adbTarget shell am start -n com.tennischannel.tceverywhere.amazon/com.foxxum.webapp.MainActivity
    sleep 6
    $adbTarget shell input keyevent 20
    sleep 1
    $adbTarget shell input keyevent 66
  else
    $adbTarget shell am start -a android.intent.action.VIEW -d https://watch.sling.com/1/channel/$channelID/watch
  fi
}

main() {
  updateReferenceFiles
  matchEncoderURL
  specialChannels
  tuneChannel
  activeAudioCheck
}

main

And sling.m3u looks like this...

#EXTINF:-1 channel-id="ESPN2" channel-number="9993" tvc-guide-stationid="45507" tvg-group="" tvg-logo="",ESPN2
http://{{ .IPADDRESS }}/play/tuner/2ad976f9aa4b4796a52ae3d64b50db9c

#EXTINF:-1 channel-id="TENNIS" channel-number="9999" tvc-guide-stationid="60316" tvg-group="" tvg-logo="",Tennis Fire TV
http://{{ .IPADDRESS }}/play/tuner/fc65b6abb023efabfaea08e47fe89beb

etc...

All works as expected. I agree with @ask_the_paid_devs, using different streamer apps (and ATV!) in the same container would be awesome.