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

I ended up just creating a new vm with new channels and ah4c instance for testing purposes.

As you said - there have been some Osprey firmware updates since last night. Seems stable at this point. Haven't tweaked it yet until everything is working. Started the DTV trial last night.

I think the only issue so far is with the guide data in Channels. No matter my settings - the channel numbers start @ 9000 and the can't get the actual guide schedule with mapping them from the source.

I also need to add all the stations that aren't in the .m3u for the Ultimate Package. I realize that all be expected to have to do anyway - except for the 9000 channel numbers. I expected them to come from the .m3u like other sources usually do.

Other than those issues - it seems pretty fast. I've also added it as a TVE source that will probably get most of what I need - except the DRM'd channels.

Thanks again!

1 Like

The same sed command as referenced here will update the M3U so that a new channel-number tag is added using the same value as channel-id:

Anytime you edit the M3U and save it using the ah4c editor, you'll need to re-run the sed command after. I'm hoping to get the M3U editor updated to include channel-number sometime soon. In the meantime, this is the fix.

3 Likes

Worked perfectly - thanks so much for the quick response!

Glad to hear it is working. My three Osprey setup has been pretty stable with no major hiccups. The last issue I ran into was when the Osprey did a major update and changed the wake feature to go to the Home Screen instead of last channel viewed. This made all the script timing off. I posted the fix earlier in this thread.

Keep us posted in the testing!

@spammedeeper

These are the scripts/osprey/directv I'm currently building with:

prebmitune.sh:

#!/bin/bash
#prebmitune.sh for osprey/directv

#Debug on if uncommented
set -x

streamerIP="$1"
streamerNoPort="${streamerIP%%:*}"
adbTarget="adb -s $streamerIP"

mkdir -p $streamerNoPort

#Trap end of script run
finish() {
  echo "prebmitune.sh is exiting for $streamerIP with exit code $?"
}

trap finish EXIT

adbConnect() {
  adb connect $streamerIP

  local -i adbMaxRetries=3
  local -i adbCounter=0

  while true; do
    $adbTarget shell input keyevent KEYCODE_WAKEUP
    local adbEventSuccess=$?

    if [[ $adbEventSuccess -eq 0 ]]; then
      break
    fi

    if (($adbCounter > $adbMaxRetries)); then
      touch $streamerNoPort/adbCommunicationFail
      echo "Communication with $streamerIP failed after $adbMaxRetries retries"
      exit 2
    fi
    

    ((adbCounter++))
  done
}

adbWake() {

    $adbTarget shell input keyevent KEYCODE_WAKEUP; sleep 2;
    echo "Waking $streamerIP"
    touch $streamerNoPort/adbAppRunning

}

main() {
  adbConnect
  adbWake
}

main

bmitune.sh:

#!/bin/bash
#bmitune.sh for osprey/directv

#Debug on if uncommented
set -x

#Global
channelID=\""$1\""
specialID="$1"
streamerIP="$2"
streamerNoPort="${streamerIP%%:*}"
adbTarget="adb -s $streamerIP"
m3uName="${STREAMER_APP#*/*/}.m3u"


#Trap end of script run
finish() {
  echo "bmitune.sh is exiting for $streamerIP with exit code $?"
}

trap finish EXIT

#Set encoderURL based on the value of streamerIP
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
}

#Tuning is based on channel name values from $m3uName.
tuneChannel() {
  
  $adbTarget shell input text $channelID; 
}


main() {
  matchEncoderURL
  tuneChannel
}

main

stopbmitune.sh:

#!/bin/bash
#stopbmitune.sh for osprey/directv

#Debug on if uncommented
set -x

streamerIP="$1"
streamerNoPort="${streamerIP%%:*}"
adbTarget="adb -s $streamerIP"

#Device sleep
adbSleep() {
  sleep="input keyevent KEYCODE_SLEEP"

  $adbTarget shell $sleep
  echo "Sleep initiated for $streamerIP"
  date +%s > $streamerNoPort/stream_stopped
  echo "$streamerNoPort/stream_stopped written with epoch stop time"
}

main() {
  adbSleep
}

main

Are these your latest?

FYI, DirecTV's app does have deep links. But they're not in an easy to guess format, and satelite, internet and stream customers have different values, and the values for the RSNs vary from area to area as they have seperate entries for out of market national and hyperlocalized feeds.

The quickest way to find them is go to to open up the developers console on a web browser, go to the network tab, navigate to stream.directv.com/guide, look for a GET request to AllChannels and use the "copy as Curl" option to download the file and open it a json viewer. Look for the "resourceId" and "callSign" for each channel you want and use the following URI layout:

dtvnow://deeplink.directvnow.com/play/channel/{callSign}/{resourceId}

i.e. TBS for satellite customers:

dtvnow://deeplink.directvnow.com/play/channel/TBSHD/86528a42-ae4e-405c-8fae-c567c1ed70df

TBS for stream/internet customers

dtvnow://deeplink.directvnow.com/play/channel/TBSHD/ded1f9a7-a3e2-503d-7129-3e31e5257fae

This will also help you with any sports alternates that have the same channel number as the main feed. The "externalListingId" value has the gracenote stationId values used by DirecTV's EPG, which will help people find the correct listings for the hyperlocalized RSN feeds in the 600s.

Unfortuantely Directv is one of the apps where the deeplink doesn't work while it's already open, so you have to use that option to force close the app between channel switches

1 Like

That's amazing news! Have you worked up any sort of an M3U yet, for whatever customer type and region you're in?

1 Like

Yes my last pull request is current, just like what you linked above. I should clarify my comment on timing.

When the last update to Osprey firmware dropped, and the wake went to Home Screen instead of last channel, this took a few second longer. This caused the key input to channel to fail. When I set the Osprey to not wake to Home Screen, all my old timing went back to normal, no change to script needed.

New bnhf/ah4c:latest (aka bnhf/ah4c:2024.05.12) pushed with updated M3U editor -- now supporting a channel-number tag. Thanks to @KompilerDJ for the update!:

1 Like

I don't seem to be getting the correct data. I think I'm following, and I see hits like this:

But when I do a Copy - Copy as cURL (cmd), and I paste that into a file -- I'm not seeing the JSON. And, a JSON viewer doesn't like it either. any idea where I might be going off the rails?:

curl ^"https://api.cld.dtvce.com/discovery/metadata/channel/v5/service/allchannels?sort=OrdCh^%^253DASC&clientContext=dmaID^%^3A753_0^%^2CbillingDmaID^%^3A753^%^2CregionID^%^3AFSAZHD_FSAPNMH_OV2+RegC+Phoenix+AZ_FSN+Arizona+SPOT_FSAZHDA_BTN4OF_BG10O2H_BTN3OF_BTN2OF_MLB+Ari+Dbacks+Choice+SPOT_BGTN4HD_BGTN3HD_BIG10HD_ARI+DBACKS+sb+RSN+SPOT_RegC+Phoenix+AZ_FSAZHD1_BS+Arizona+OOM+B^%^2FO_FSN+Arizona+RSN+SPOT_OV+Phoenix+AZ+753_OV+Phoenix+AZ+DMA+753_OV+MeTV+Allowed+SPOT^%^2CzipCode^%^3A85268^%^2CcountyCode^%^3A013^%^2CstateNumber^%^3A4^%^2CstateAbbr^%^3AAZ^%^2CusrLocAndBillLocAreSame^%^3Atrue^%^2CbRegionID^%^3AFSAZHD_FSAPNMH_OV2+RegC+Phoenix+AZ_FSN+Arizona+SPOT_FSAZHDA_BTN4OF_BG10O2H_BTN3OF_BTN2OF_MLB+Ari+Dbacks+Choice+SPOT_BGTN4HD_BGTN3HD_BIG10HD_ARI+DBACKS+sb+RSN+SPOT_RegC+Phoenix+AZ_FSAZHD1_BS+Arizona+OOM+B^%^2FO_FSN+Arizona+RSN+SPOT_OV+Phoenix+AZ+753_OV+Phoenix+AZ+DMA+753_OV+MeTV+Allowed+SPOT^%^2CdeviceProximity^%^3AOOH&fisProperties=ISF^%^3A1.3^%^23chlogo-bwdb-mytv^%^2C47^%^2C35^%^23chlogo-clb-guide^%^2C60^%^2C45^%^23chlogo-cdb-gcd^%^2C87^%^2C66^%^23chlogo-bwdb-player^%^2C120^%^2C91^%^23chlogo-bwdb-fplayer^%^2C120^%^2C91&include4K=true&is4KCompatible=false&isFtue=true&_tz=1715543115564^" ^
  -H "accept: */*" ^
  -H "accept-language: en-US,en;q=0.9" ^
  -H "authorization: Bearer ceWKoHXohpc6MST2LDOWmQazUgy2mtEHHzeTWU5RyYfgHtALeOdZhFsvmPmWGYoz0e3q6waStlIhydHH8TwOAiu1P7KjX4RAIAXNgPsSynRWhUjZV6msuMpK+ZPshDgYtHMRXz8EoF5o4fkCUqaix3Mu/U74rWG1OcZfF0roYO6C3i4lhDVZ95utXbKGoH+EDQ5swO+14pYdOukS1raNYZZ7w7qsj2wAgOsrYzGCA3VZiZXPKlztpOgco3OTyswX5Y8Ph936UI+AJ1zqEe+QXTBeWVlz+JbAF5tmrWEc9P/ZYkE3vbazMI3tXYQ/7SU578yz4MPW2zUaVuGZ4N/AztOh9Jg35Ru1rjFGSrn7GbSHBw0KYVRlcCI9IBTY+HBAPuihwGDi31mJIjz4HQFWRZ/u3PffqpLzos+BClPeItNMTqX8kJp2HlJE52uTP9OW1xBnAbu3mxvcCEmk6CYi+sePtOb7Y4PH68/hXGDG0/PU2i/PQZeclOE/OuIaxh/7xl3nvHsCKll/EOSigyxPNxQ7L5qxpYW9LCHPIzSvxy0Brw0uUKwmFjO8/wiap4j3jpTJD/0RoRmepTbE51KC5lxRvSBFRkKcLOlnKn8LELDr+MvpNVPYxMoLSmfn/EGUvAsgaeATueDT9Fj9+qn9Mr6sdxZymS4dgBJSDdNBDyMc0xqlwz/d8Rp/WhK7b+pZTRfrh4RCtDMBsF2ZINYsxoPb82nb4VBhDA/6VCH8pMjCL9+w+vCsChzuG4Bw7q7/hlnOufnom/GdLE2kbnJjILWcArfTG1mlXAvFrD5259vDGr68ppJjpDsGKDuSBQ3U5TQC4YKHWRh4PyMLG2COb8mo3ZOlRrcizIyNDsNsQYVzG7C4QGV+xJ0VKzsLFV3w16oYp1GV9mI5T/aqBBeFg48Qyl83m38/S1Wx0dwyVJi2J2dXZnM7hNYbJ9t7Lpyl9L/bnnv9rjEDmWT5VOkh9algazGaFekIbO6hBSszCDEd2TXgFyFSxs1ImM6cF+I+lAkAWug4lufQiSS7+qBNHgBbWCmISFb+QSLWf7CP+h1SylYfU9xEomUUBxY0qQJqxfwxbACAI3gRXaWvzDzjQiYzDrc/qdGblY/WxoMX9No=.rp/9WhT7ah2wmhDLdjbD74gWk89JLAw9QN+b2j0BH0A=" ^
  -H "cache-control: no-cache,no-store,max-age=0,must-revalidate,reload" ^
  -H "origin: https://stream.directv.com" ^
  -H "priority: u=1, i" ^
  -H "referer: https://stream.directv.com/" ^
  -H ^"sec-ch-ua: ^\^"Chromium^\^";v=^\^"124^\^", ^\^"Google Chrome^\^";v=^\^"124^\^", ^\^"Not-A.Brand^\^";v=^\^"99^\^"^" ^
  -H "sec-ch-ua-mobile: ?0" ^
  -H ^"sec-ch-ua-platform: ^\^"Windows^\^"^" ^
  -H "sec-fetch-dest: empty" ^
  -H "sec-fetch-mode: cors" ^
  -H "sec-fetch-site: cross-site" ^
  -H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"

Looks like a curl command I should then run, but it's not quite right for that either...

You have to paste the curl command in a terminal and add the "-o filename.json" flag to output it

1 Like

Sweet, thanks! Trying to do it using Windows curl wasn't happening for some reason, but looks like I got it switching to Debian and bash.

Something weird when using XFNITY ... It asked me if I wanted to open the link in STREAM or SILK Browser ... I had to answer to always use STREAM ... I do not get this using ADBTUNER.

That's expected behavior. It only happens once per FireStick, assuming you select the option to use Stream (Xfinity Stream) every time.

OK because I have never seen this before when provisioning a new FiresTick ... even when i use my own script to test.

this is what I use to launch XFINITY without getting that Prompt.

set "channel_url=https://www.xfinity.com/stream/live/HBOHW/5232923399725817105/HBOHW"

adb -s %device% shell am start -n com.xfinity.cloudtvr.tenfoot/com.xfinity.common.view.LaunchActivity "%channel_url%"

That'd be an easy enough change to make. I'll do that for the next ah4c build.

1 Like

New bnhf/ah4c:latest (aka bnhf/ah4c:2024.05.14) built today with support for DirecTV deep links (many thanks to @kyl416 for his discovery!). The included M3U contains satellite customer ResourceIDs for the Phoenix area. If you're a stream customer, and/or from a different area, you'll need to use the method described a few posts back to get your callSign and resourceId values for each channel.

For now anyway, I'm using scripts/firetv/dtvdeeplinks as the STREAMER_APP. Once someone has done, and submitted an M3U for DTVStream, I'll clone a set of companion scripts for stream accounts.

1 Like

New :latest and :2024.05.15 pushed today with tuning speed improvements for Xfinity and DTV (Satellite) accounts. Down to about 8 seconds for DTV with a FireStick 4K Max 2 as the target device.

Both seem to be responding well to leaving their respective apps running at the end of a virtual tune, provided KEYCODE_BACK and KEYCODE_HOME are executed first.

Since it's still possible to have a failed tune based on bad M3U data or whatever, a screen capture is being done after x seconds with an OCR scan performed. If the results of that scan match words found on either app's default "landing screen", the app will be killed and a re-tune performed.

Results have been encouraging in initial testing. Credit to @tree2369 for the idea.

It works but it is a really slow in closing the stream. I was watching also on VLC took a while to close the playing stream. It takes over 10 seconds to close the Stream.

We can play around with that. In the case of Xfinity Stream, it takes around 35 seconds to tune if the app is closed. So, I'm waiting 40 seconds before doing the screen capture and OCR, in case the virtual tune started with the app killed.

This is the function in bmitune.sh that's doing the check:

tuneCheck() {
  sleep 40
  ffmpeg -i $encoderURL -frames:v 1 -y $streamerNoPort/screencapture.jpg -loglevel quiet
  tesseract $streamerNoPort/screencapture.jpg $streamerNoPort/screencapture
  grep -q "Filter\|Today" $streamerNoPort/screencapture.txt

  if [ $? == 0 ]; then
    echo "Deeplink tuning appears to have failed. Killing app and re-tuning..."
    $adbTarget shell am force-stop $packageName
    tuneChannel
  else
    echo "Deeplink tuning appears to have been successful!"
  fi
}

Also, if you want get rid of the OCR check altogether, you can simply comment out tuneCheck in the main() function. The downside is that it's a hassle, if the virtual tune fails, to get back on track without something like this.

EDIT: BTW, if the OCR scan is complete, closing the app shouldn't take any longer than before. If you stop the stream before that's done though, the scan will complete and the all-important KEYCODE values sent before the virtual tuner is available again. If you have more than one tuner, tuning to something else should grab the next device in line.