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

The original HDMI for Channels thread is super long, and this particular variant (dockerized version of ah4c plus ws-scrcpy) bears only a passing resemblance to the original project. So, I thought it might be time to create a new thread that highlights what the capabilities are of this very useful extension.

There are two projects that use this same combination of HDMI encoder(s) + streaming stick(s) as a source for Channels DVR. This project, ah4c, and ADBTuner. ADBTuner is great for any streaming app that supports deep links, while ah4c supports both deep links and remote control emulation.

Deep links allow for reasonably fast virtual tuning, while sending Android Debug Bridge (adb) commands to simulate keypresses is typically slower. The main advantage to using adb commands in script form, is that pretty much any streaming app running on a streaming stick, connected to an HDMI encoder, can become a source for a virtual tuner.

This dockerized version of ah4c uses Portainer, so you'll want to be sure you have this excellent WebUI for Docker installed before beginning. Here's the current recommended docker-compose for Portainer-Stacks:

version: '3.9'
services:
  ah4c:
    image: bnhf/ah4c:${TAG}
    container_name: ah4c
    hostname: ah4c
    dns_search: ${DOMAIN} # Specify the name of your LAN's domain, usually local or localdomain
    ports:
      #- 5037:5037 # Port used by adb-server
      - 7654:7654 # Port used by this androidhdmi-for-channels proxy
      - 7655:8000 # Port used by ws-scrcpy
    environment:
      - IPADDRESS=${IPADDRESS} # Hostname or IP address of this androidhdmi-for-channels 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
      - 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
      - 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="" # For advanced users, tuning failure alerts can be sent via a webhook.
      - LIVETV_ATTEMPTS=${LIVETV_ATTEMPTS} # For FireTV Live Guide tuning only, set maximum number of attempts at finding the desired channel
      - CREATE_M3US=${CREATE_M3US} # Set to true to create device-specific M3Us for use with Amazon Prime Premium channels -- requires a FireTV device
      - TZ=${TZ} # Your local timezone in Linux "tz" format
    volumes:
      - /${HOST_DIR}/ah4c/scripts:/opt/scripts # pre/stop/bmitune.sh scripts will be stored in this bound host directory under streamer/app
      - /${HOST_DIR}/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}/ah4c/adb:/root/.android # Persistent data directory for adb keys
    restart: unless-stopped

Most anything that'll be unique to your installation is setup to use environment variables. Here's a set of example environment variables you'll want customize to your particular setup:

TAG=latest
DOMAIN=localdomain
IPADDRESS=htpc6:7674
NUMBER_TUNERS=1
TUNER1_IP=firestick-rack1:5555
ENCODER1_URL=http://encoder_00000/0.ts
STREAMER_APP=scripts/firetv/directv
CHANNELSIP=media-server6
ALERT_SMTP_SERVER=smtp.gmail.com:587
ALERT_AUTH_SERVER=smtp.gmail.com
[email protected]
ALERT_EMAIL_PASS=yourgmailapppassword
[email protected]
LIVETV_ATTEMPTS=25
CREATE_M3US=false
TZ=US/Mountain
HOST_DIR=/data

These variables can be entered in the form above using Advanced mode, and will look similar to this in Simple mode:

Be sure to review the comments in the docker-compose above for additional hints on how to define these all-important environment variables.

There are existing scripts for a number of combinations of streamer/app. For example combinations like scripts/firetv/directv, scripts/osprey/directv, scripts/firetv/livetv will all place tuning scripts and M3Us in the directory on your Docker host you have mapped to /opt/scripts and /opt/m3u.

There are other existing combinations as well, but if there are no current scripts for your desired combination of streamer/app, then you'll need to create them. scripts/firetv/directv is a good place to start, as this set of scripts has many Bash functions that can be re-used. Look in the scripts folder at:

to get an idea of what's available. (Many thanks to @KompilerDJ for his excellent work!)

Once you have ah4c up-and-running, complete with functional tuning scripts and M3U, you can add this virtual tuner in the Custom Channels section of Channels DVR:

There are numerous tools built-in to this container to give you an idea what's happening with your virtual tuning:

The main page can be found at the address you specified for $IPADDRESS:

And a few sample pages:

Activity & logs:

Edit Channel M3Us:

In addition a browser based version of scrcpy is also included, using a common adb authorization database with ah4c. Be aware, DRM is not supported with this tool though, so you'll see all the menus and can open apps, but none of the DRM protected content will display. Even with that limitation, it's amazingly useful, particularly for remote access and control of your sticks:

This can be found at the port you specified in your docker-compose (usually 7655). All of the streaming sticks defined in your docker-compose will be listed:

Click on shell, and you'll have an adb shell in a new tab where you can issue commands to your selected streaming stick. Back in the original tab, click on Configure stream, followed by:

And you'll see whatever your HDMI-connected streaming stick is displaying:

There's a button to passthrough keyboard commands, along with Home, Back and Screenshot buttons. Any commands you can't generate on the keyboard you can send from the shell. Examples of these would be:

input keyevent KEYCODE_SLEEP
input keyevent KEYCODE_MENU

bnhf/ah4c:latest should now be fully up-to-date, and can be used in place of ah4c:prime or ah4c:test2

Hopefully this will be enough to get anyone going that's not already familiar with this extension, but if not, post your questions or comments here. Positive discourse only please! :slight_smile:

3 Likes

@spammedeeper I noticed when I was building the new unified version of ah4c today, including your osprey/directv scripts, that you didn't post your M3U. It's be great if you could post that here, and I'll add it to the container and the repo.

Also for anyone thinking about gathering the pieces and parts to make an ah4c or ADBTuner project happen, I thought I'd add a little something about keeping the hardware organized.

Mine is rack-mounted, though that's not a requirement -- it just makes it a bit nicer. Here I'm using a ventilated 1U shelf, combined with a 1U 16-port patch panel. Then add RJ-45, USB and HDMI keystone jacks, some minimum length cables, PoE-capable OTG Ethernet adapters, a PoE splitter for the HDMI encoder, and a handful of wire-ties.

The end result is well-ventilated, easy to maintain or upgrade -- and for me anyway, fits in great with my other rack mounted gear. Everything is PoE powered, so it'd be easy to power cycle any component -- if ever needed:

2 Likes

Posted: https://github.com/Spamington/ah4c/blob/main/m3u/osprey-dtv-entertainment.m3u

I think it already committed to main. This M3U is for the DirectTV Stream "Entertainment" package. I have not gone back to update GraceNote Station IDs, but will in a future update (when I do mine), but all the channel numbers match DirecTV, which is what I'm used to.

Let me know if you need anything else. Thanks for all the great work.

EDIT: Correction, it did not merge to main, but I did put in a pull request.

4 Likes

Merged -- thanks for the contribution! One modification I made after the merge, that I'll highlight here for the benefit of anyone else creating an M3U for this project:

There's no need to hardcode the hostname:port or IP:port into the M3U, in fact it's undesirable. By using a variable we've defined of {{ .IPADDRESS }}, what ever value you've defined as $IPADDRESS in the docker-compose will be used when the stream is requested.

This way, a single M3U will work if you have multiple DVRs or multiple ah4c containers, and can also be shared directly with others without modification.

Here's the revised M3U, dtvosprey.m3u which can also be found in the repo:

#EXTM3U

#EXTINF:-1 channel-id="265" channel-number="265",A&E
http://{{ .IPADDRESS }}/play/tuner/265

#EXTINF:-1 channel-id="361" channel-number="361",AccuWeather
http://{{ .IPADDRESS }}/play/tuner/361

#EXTINF:-1 channel-id="254" channel-number="254",AMC
http://{{ .IPADDRESS }}/play/tuner/254

#EXTINF:-1 channel-id="282" channel-number="282",Animal Planet
http://{{ .IPADDRESS }}/play/tuner/282

#EXTINF:-1 channel-id="340" channel-number="340",AXS TV
http://{{ .IPADDRESS }}/play/tuner/340

#EXTINF:-1 channel-id="293" channel-number="293",BabyFirst HD
http://{{ .IPADDRESS }}/play/tuner/293

#EXTINF:-1 channel-id="264" channel-number="264",BBC America
http://{{ .IPADDRESS }}/play/tuner/264

#EXTINF:-1 channel-id="329" channel-number="329",BET
http://{{ .IPADDRESS }}/play/tuner/329

#EXTINF:-1 channel-id="353" channel-number="353",Bloomberg TV
http://{{ .IPADDRESS }}/play/tuner/353

#EXTINF:-1 channel-id="298" channel-number="298",Boomerang
http://{{ .IPADDRESS }}/play/tuner/298

#EXTINF:-1 channel-id="237" channel-number="237",Bravo
http://{{ .IPADDRESS }}/play/tuner/237

#EXTINF:-1 channel-id="296" channel-number="296",Cartoon Network East
http://{{ .IPADDRESS }}/play/tuner/296

#EXTINF:-1 channel-id="341" channel-number="341",CleoTV
http://{{ .IPADDRESS }}/play/tuner/341

#EXTINF:-1 channel-id="327" channel-number="327",CMT
http://{{ .IPADDRESS }}/play/tuner/327

#EXTINF:-1 channel-id="355" channel-number="355",CNBC
http://{{ .IPADDRESS }}/play/tuner/355

#EXTINF:-1 channel-id="202" channel-number="202",CNN
http://{{ .IPADDRESS }}/play/tuner/202

#EXTINF:-1 channel-id="419" channel-number="419",CNN En Español
http://{{ .IPADDRESS }}/play/tuner/419

#EXTINF:-1 channel-id="358" channel-number="358",CNNi
http://{{ .IPADDRESS }}/play/tuner/358

#EXTINF:-1 channel-id="249" channel-number="249",Comedy Central
http://{{ .IPADDRESS }}/play/tuner/249

#EXTINF:-1 channel-id="350" channel-number="350",C-SPAN
http://{{ .IPADDRESS }}/play/tuner/350

#EXTINF:-1 channel-id="351" channel-number="351",C-SPAN2
http://{{ .IPADDRESS }}/play/tuner/351

#EXTINF:-1 channel-id="369" channel-number="369",Daystar
http://{{ .IPADDRESS }}/play/tuner/369

#EXTINF:-1 channel-id="278" channel-number="278",Discovery
http://{{ .IPADDRESS }}/play/tuner/278

#EXTINF:-1 channel-id="290" channel-number="290",Disney Channel
http://{{ .IPADDRESS }}/play/tuner/290

#EXTINF:-1 channel-id="289" channel-number="289",Disney Junior
http://{{ .IPADDRESS }}/play/tuner/289

#EXTINF:-1 channel-id="292" channel-number="292",Disney XD
http://{{ .IPADDRESS }}/play/tuner/292

#EXTINF:-1 channel-id="236" channel-number="236",E!
http://{{ .IPADDRESS }}/play/tuner/236

#EXTINF:-1 channel-id="267" channel-number="267",EarthxTV
http://{{ .IPADDRESS }}/play/tuner/267

#EXTINF:-1 channel-id="206" channel-number="206",ESPN
http://{{ .IPADDRESS }}/play/tuner/206

#EXTINF:-1 channel-id="209" channel-number="209",ESPN2
http://{{ .IPADDRESS }}/play/tuner/209

#EXTINF:-1 channel-id="314" channel-number="314",Family Movie Classics (FMC)
http://{{ .IPADDRESS }}/play/tuner/314

#EXTINF:-1 channel-id="231" channel-number="231",Food Network
http://{{ .IPADDRESS }}/play/tuner/231

#EXTINF:-1 channel-id="359" channel-number="359",FOX Business Network
http://{{ .IPADDRESS }}/play/tuner/359

#EXTINF:-1 channel-id="360" channel-number="360",FOX News Channel
http://{{ .IPADDRESS }}/play/tuner/360

#EXTINF:-1 channel-id="219" channel-number="219",FOX Sports 1
http://{{ .IPADDRESS }}/play/tuner/219

#EXTINF:-1 channel-id="363" channel-number="363",FOX Weather
http://{{ .IPADDRESS }}/play/tuner/363

#EXTINF:-1 channel-id="311" channel-number="311",Freeform
http://{{ .IPADDRESS }}/play/tuner/311

#EXTINF:-1 channel-id="248" channel-number="248",FX
http://{{ .IPADDRESS }}/play/tuner/248

#EXTINF:-1 channel-id="259" channel-number="259",FXX
http://{{ .IPADDRESS }}/play/tuner/259

#EXTINF:-1 channel-id="404" channel-number="404",Galavisión
http://{{ .IPADDRESS }}/play/tuner/404

#EXTINF:-1 channel-id="312" channel-number="312",Hallmark Channel
http://{{ .IPADDRESS }}/play/tuner/312

#EXTINF:-1 channel-id="565" channel-number="565",Hallmark Movies & Mysteries
http://{{ .IPADDRESS }}/play/tuner/565

#EXTINF:-1 channel-id="229" channel-number="229",HGTV
http://{{ .IPADDRESS }}/play/tuner/229

#EXTINF:-1 channel-id="204" channel-number="204",HLN
http://{{ .IPADDRESS }}/play/tuner/204

#EXTINF:-1 channel-id="240" channel-number="240",HSN
http://{{ .IPADDRESS }}/play/tuner/240

#EXTINF:-1 channel-id="333" channel-number="333",IFC
http://{{ .IPADDRESS }}/play/tuner/333

#EXTINF:-1 channel-id="285" channel-number="285",Investigation Discovery
http://{{ .IPADDRESS }}/play/tuner/285

#EXTINF:-1 channel-id="305" channel-number="305",ION
http://{{ .IPADDRESS }}/play/tuner/305

#EXTINF:-1 channel-id="252" channel-number="252",Lifetime
http://{{ .IPADDRESS }}/play/tuner/252

#EXTINF:-1 channel-id="281" channel-number="281",MotorTrend
http://{{ .IPADDRESS }}/play/tuner/281

#EXTINF:-1 channel-id="356" channel-number="356",MSNBC
http://{{ .IPADDRESS }}/play/tuner/356

#EXTINF:-1 channel-id="331" channel-number="331",MTV
http://{{ .IPADDRESS }}/play/tuner/331

#EXTINF:-1 channel-id="332" channel-number="332",MTV2
http://{{ .IPADDRESS }}/play/tuner/332

#EXTINF:-1 channel-id="276" channel-number="276",National Geographic Channel
http://{{ .IPADDRESS }}/play/tuner/276

#EXTINF:-1 channel-id="349" channel-number="349",Newsmax
http://{{ .IPADDRESS }}/play/tuner/349

#EXTINF:-1 channel-id="299" channel-number="299",Nickelodeon
http://{{ .IPADDRESS }}/play/tuner/299

#EXTINF:-1 channel-id="274" channel-number="274",Ovation
http://{{ .IPADDRESS }}/play/tuner/274

#EXTINF:-1 channel-id="241" channel-number="241",Paramount Network
http://{{ .IPADDRESS }}/play/tuner/241

#EXTINF:-1 channel-id="288" channel-number="288",PBS Kids
http://{{ .IPADDRESS }}/play/tuner/288

#EXTINF:-1 channel-id="275" channel-number="275",QVC
http://{{ .IPADDRESS }}/play/tuner/275

#EXTINF:-1 channel-id="315" channel-number="315",QVC2
http://{{ .IPADDRESS }}/play/tuner/315

#EXTINF:-1 channel-id="318" channel-number="318",QVC3
http://{{ .IPADDRESS }}/play/tuner/318

#EXTINF:-1 channel-id="238" channel-number="238",Reelz
http://{{ .IPADDRESS }}/play/tuner/238

#EXTINF:-1 channel-id="384" channel-number="384",Revolt
http://{{ .IPADDRESS }}/play/tuner/384

#EXTINF:-1 channel-id="345" channel-number="345",RFD-TV
http://{{ .IPADDRESS }}/play/tuner/345

#EXTINF:-1 channel-id="320" channel-number="320",Scientology Network
http://{{ .IPADDRESS }}/play/tuner/320

#EXTINF:-1 channel-id="226" channel-number="226",Shop LC
http://{{ .IPADDRESS }}/play/tuner/226

#EXTINF:-1 channel-id="239" channel-number="239",SundanceTV
http://{{ .IPADDRESS }}/play/tuner/239

#EXTINF:-1 channel-id="244" channel-number="244",Syfy
http://{{ .IPADDRESS }}/play/tuner/244

#EXTINF:-1 channel-id="247" channel-number="247",TBS
http://{{ .IPADDRESS }}/play/tuner/247

#EXTINF:-1 channel-id="256" channel-number="256",TCM
http://{{ .IPADDRESS }}/play/tuner/256

#EXTINF:-1 channel-id="303" channel-number="303",TeenNick
http://{{ .IPADDRESS }}/play/tuner/303

#EXTINF:-1 channel-id="347" channel-number="347",The First
http://{{ .IPADDRESS }}/play/tuner/347

#EXTINF:-1 channel-id="269" channel-number="269",The HISTORY Channel
http://{{ .IPADDRESS }}/play/tuner/269

#EXTINF:-1 channel-id="342" channel-number="342",TheGrio
http://{{ .IPADDRESS }}/play/tuner/342

#EXTINF:-1 channel-id="280" channel-number="280",TLC
http://{{ .IPADDRESS }}/play/tuner/280

#EXTINF:-1 channel-id="245" channel-number="245",TNT
http://{{ .IPADDRESS }}/play/tuner/245

#EXTINF:-1 channel-id="246" channel-number="246",truTV
http://{{ .IPADDRESS }}/play/tuner/246

#EXTINF:-1 channel-id="304" channel-number="304",TV Land
http://{{ .IPADDRESS }}/play/tuner/304

#EXTINF:-1 channel-id="242" channel-number="242",USA Network
http://{{ .IPADDRESS }}/play/tuner/242

#EXTINF:-1 channel-id="335" channel-number="335",VH1
http://{{ .IPADDRESS }}/play/tuner/335

#EXTINF:-1 channel-id="271" channel-number="271",VICE
http://{{ .IPADDRESS }}/play/tuner/271

#EXTINF:-1 channel-id="260" channel-number="260",WE tv
http://{{ .IPADDRESS }}/play/tuner/260
1 Like

Another item worth referencing in this new thread, is an ah4c thread dedicated to those that want to turn premium channels subscribed to through Amazon Prime into a source for Channels. ah4c is still the tool, but there is some special prep required for any FireSticks you want to use for this purpose:

Noted and thanks! I originally had issue with my Docker Compose and getting the M3U editor to work so I did it manually.

On this topic, if anyone with DTV Stream is interested in trying Osprey boxes, I’d be happy to help. They are cheap, have Ethernet and have the advantage of targeting a channel number for tuning making them very reliable.

1 Like

A couple of questions on these:

Do you know if the Osprey boxes need to be added to your DirecTV account if you're a DTV satellite customer?

And, can they be used in more than one location, if you're either a DTV or DTVStream customer?

Happy to share. Let me give everyone quick history on these boxes before jumping to your questions.

These Osprey boxes were released 2019/2020 as part of the AT&T Now service. The goal was a cable box like experience, and that works well. They were then firmware upgraded to DirecTV Now, and once again just recently to be DirecTV Stream Gemini boxes. The last firmware upgrade and rename was to match the existing DirecTV branding. They are all the same hardware.

The hardware itself is a bit dated and not terribly fast, but they work just fine. To be fair, the last nVidia Shield Pro was released in 2019, and still gets updates. These Osprey boxes are still supported and on Android 11. I suspect they will be supported for a long time. They only new hardware is the recently released Gemini Air, which is a dongle style adapter much like a Chromecast. The performance and experience are the same as the older Osprey boxes. They even use the same remote.


To your questions:

Do you know if the Osprey boxes need to be added to your DirecTV account if you're a DTV satellite customer?

Maybe? Not sure they need to be on your account like a Sat box. I have DirecTV Stream, not Satellite so I can't test, but assume if you can log into the DirecTV app with sat creds for streaming, the Osprey will work. The Osprey Box is just the DTV App as a launcher. I have seen mention on Reddit of users with DirecTV Satellite using an Osprey Box as a secondary streamer.

REF: https://www.reddit.com/r/DirectvStream/comments/10rrk7m/osprey_box_for_directv_satellite/

NOTE: There is a Gemini/Osprey box that is exactly like the streaming only version save for a kit that comes with it for satellite. It is firmware locked, so I've read, to not work with Streaming. Just stick with the model I will list later in post.

And, can they be used in more than one location, if you're either a DTV or DTVStream customer?

Yes, remote with limits. This is one of the big advantages of DTV Stream. They allow up to 20 devices in your "home" network, then three remote streams outside of home, with two of those streams being devices.

I can confirm this as DirecTV Stream customer. It does allow remote devices on my account. In my second location, I can stream with the Osprey boxes, up to two, without getting hit with a stream limit. Not sure if this applies to DTV Sat.


Now that I've shared, and I'm sure some people are interested, I want to be clear on my advice. For anyone using DirecTV Stream, the Osprey boxes are an excellent match for AH4C. But before you run out to find one, let me share some info, starting with concerns.

  1. The STB style Osprey hardware is not made any more and you can't buy the older Osprey boxes from DirecTV*. Only available a used only on eBay.

  2. The boxes are now 4+ years old. I don't know how long the firmware support from DirecTV will be, but I doubt they are going to cut off thousands (millions?) of users any time soon.

  3. These boxes are paperweights without an DirecTV login. If you order one, and cancel DTV, they have no use.

  4. When buying used, you have to find a reputable buyer. If you get a used box with the AT&T or DirecTV NOW Firmware, it won't update to DirecTV Stream. Be cautious when selecting a source for used hardware.

NOTE: For a small set of DirecTV Stream users, who have an older account that carried over from AT&T or DirecTV NOW, you can actually order a refurb Osprey box for $50 from DirecTV. This comes with one year warranty. You'd have to look at your DTV account under "devices" to confirm, or contact support.

With all the disclaimer stuff out of the way, let me share how cool the devices are.

  1. Used on eBay, they are cheap. $25-50.
  2. They have Ethernet!
  3. They have USB, which allows keyboard setup without remote (or for ADB use, I suppose)
  4. The target for tuning stations is a channel number, which is very unlikely to change like deep links.
  5. The automation with AH4C is very simple and reliable: wake Osprey, tune channel, sleep Osprey. No apps to open, crash or hang.

If anyone wants to pick one up for testing, here is link to an eBay vendor I purchased from. They were recommend by Reddit as a source for Osprey Boxes with the newest DTV Stream firmware.

AT&T DirecTV Android TV Wireless 4K OTT Client Set-Top Box Player - $41.50

Note the vendor above has many options: Just the Box, Box and Power, or full box, power and remote for different prices.

Hope this helps. Happy Streaming.

I just installed it on Docker for Windows. I believe I got all the settings correctly, and I do see the adb/scripts and m3u directories on my windows system, however under the scripts directory I only see onn, I don't see any scripts for any of the other devices and or services as examples. Did I do something wrong? I did pull the latest version I believe.

Oh, and on the web interface there is one link that is not working, the one that says edit ENV Configurations and Tuners. I get an error page saying :This page is not working right now error empty response.

Two sets of scripts get copied over: scripts/onn/youtube (which were the original example scripts for this project), and whatever you specified as $STREAMER_APP (assuming anything exists to put in a scripts/streamer/app folder).

This is probably not the best approach any longer, as the scripts I wrote for firetv/directv are a much better starting point. I'll fix that in the next build. In the meantime, time you can copy and paste them from the github.com/sullrich/ah4c page -- from the scripts folder, and I'll post firetv/directv here:

prebmitune.sh

#!/bin/bash
#prebmitune.sh for firetv/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=25
  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 1
    fi

    sleep 1
    ((adbCounter++))
  done
}

adbWake() {
  packageLaunch="com.clientapp.MainActivity"
  packageName="com.att.tv"
  packagePID=$($adbTarget shell pidof $packageName)
  
  if [ ! -z $packagePID ]; then
    $adbTarget shell input keyevent KEYCODE_WAKEUP
    $adbTarget shell am start -n $packageName/$packageLaunch
    echo "Waking $streamerIP"
    touch $streamerNoPort/adbAppRunning
  else
    $adbTarget shell input keyevent KEYCODE_WAKEUP
    $adbTarget shell am start -n $packageName/$packageLaunch
    echo "Starting $packageName on $streamerIP"
  fi
}

main() {
  adbConnect
  adbWake
}

main

bmitune.sh

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

#Debug on if uncommented
set -x

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

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

trap finish EXIT

updateReferenceFiles() {

  # Handle cases where stream_stopped or last_channel don't exist
  mkdir -p $streamerNoPort
  [[ -f "$streamerNoPort/stream_stopped" ]] || echo 0 > "$streamerNoPort/stream_stopped"
  [[ -f "$streamerNoPort/last_channel" ]] || echo 0 > "$streamerNoPort/last_channel"

  # Write PID for this script to bmitune_pid for use in stopbmitune.sh
  echo $$ > "$streamerNoPort/bmitune_pid"
  echo "Current PID for this script is $$"
}

#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
}

#Check for active audio stream with maxDuration, preTuneAudioCheck, sleepBeforeAudioCheck and sleepAfterAudioCheck as arguments
activeAudioCheck() {
  local startTime=$(date +%s)
  local maxDuration=$1
  local minimumLoudness=-50
  local sleepBeforeAudioCheck=$3
  local sleepAfterAudioCheck=$4
  local preTuneAudioCheck=$2
  
  while true; do
    sleep $sleepBeforeAudioCheck
    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."
      if [ $preTuneAudioCheck = "false" ]; then
        echo "Active audio stream not detected after tuning completed"
        case "$specialID" in
          "212")
            echo "Possible sports event blackout on NFL Network, so bumping channel up"
            $adbTarget shell input keyevent KEYCODE_DPAD_LEFT
            echo 0 > "$streamerNoPort/last_channel"
            exit 1
            ;; 
          "213")
            echo "Possible sports event blackout on MLB Network, so bumping channel down"
            $adbTarget shell input keyevent KEYCODE_DPAD_RIGHT
            echo 0 > "$streamerNoPort/last_channel"
            exit 1
            ;; 
          *)
            echo "Possible sports event blackout, so bumping channel down"
            $adbTarget shell input keyevent KEYCODE_DPAD_RIGHT
            echo 0 > "$streamerNoPort/last_channel"
            exit 1
            ;;
        esac
      else
        exit 1
      fi
    fi

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

    echo "Active audio stream not yet detected -- loudness is $checkLoudness LUF. Continuing..."
    sleep $sleepAfterAudioCheck
  done
}

#Special channels to kill DirecTV app or reboot FireStick
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)"
      appFocus=$($adbTarget shell dumpsys window windows | grep -E 'mCurrentFocus' | cut -d '/' -f1 | sed 's/.* //g')
      echo "Current app in focus is $appFocus" 
    fi
}

#Variable delay based on whether app was running or needed to be launched
#and whether less than maxTime seconds (maxTime/3600 for hours) has passed while sleeping
launchDelay() {
  local lastChannel
  local lastAwake
  local timeNow
  local timeElapsed
  local maxTime=14400

  lastChannel=$(<"$streamerNoPort/last_channel")
  lastAwake=$(<"$streamerNoPort/stream_stopped")
  timeNow=$(date +%s)
  timeElapsed=$(($timeNow - $lastAwake))

  if (( $lastChannel == $specialID )) && (( $timeElapsed < $maxTime )); then
    echo "Last channel selected on this tuner, no channel change required"
    exit 0
  elif [ -f $streamerNoPort/adbAppRunning ] && (( $timeElapsed < $maxTime )); then
    activeAudioCheck 42 true 0 1 # (maxDuration, preTuneAudioCheck, sleepBeforeAudioCheck, sleepAfterAudioCheck)
    #sleep 14
    rm $streamerNoPort/adbAppRunning
    echo $specialID > "$streamerNoPort/last_channel"
  else
    activeAudioCheck 42 true 0 1 # (maxDuration, preTuneAudioCheck, sleepBeforeAudioCheck, sleepAfterAudioCheck)
    #sleep 32
    echo $specialID > "$streamerNoPort/last_channel"
  fi
}

#Tuning is based on channel name values from $m3uName.
tuneChannel() {
  channelName=$(awk -F, '/channel-id='"$channelID"'/ {print $2}' m3u/$m3uName)
  channelName=$(echo $channelName | sed 's/^/"/;s/$/"/')
  numberOfBackspaces=25
  clearSearchBackspaces=$(for ((i=0; i<$numberOfBackspaces; i++)); do echo -n " KEYCODE_MEDIA_REWIND"; done)

  directvMenu="input keyevent KEYCODE_MENU; sleep 6"

  directvSearch="input keyevent KEYCODE_DPAD_LEFT; \
                 input keyevent KEYCODE_DPAD_UP; \
                 input keyevent KEYCODE_DPAD_CENTER; sleep 1; \
                 input keyevent KEYCODE_DPAD_CENTER; sleep 1"

  directvClearSearch="input keyevent$clearSearchBackspaces"

  directvTune="input keyevent KEYCODE_MEDIA_PLAY_PAUSE; sleep 1; \
               input keyevent KEYCODE_DPAD_DOWN; \
               input keyevent KEYCODE_DPAD_DOWN; \
               input keyevent KEYCODE_DPAD_DOWN; \
               input keyevent KEYCODE_DPAD_CENTER"

  $adbTarget shell $directvMenu
  $adbTarget shell $directvSearch
  $adbTarget shell $directvClearSearch
  $adbTarget shell input text "$channelName"
  $adbTarget shell $directvTune
}

main() {
  updateReferenceFiles
  matchEncoderURL
  specialChannels
  launchDelay
  tuneChannel
  activeAudioCheck 24 false 5 1 # (maxDuration, preTuneAudioCheck, sleepBeforeAudioCheck, sleepAfterAudioCheck)
}

main

stopbmitune.sh

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

#Debug on if uncommented
set -x

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

#Check if bmitune.sh is done running
bmituneDone() {
  bmitunePID=$(<"$streamerNoPort/bmitune_pid")

  while ps -p $bmitunePID > /dev/null; do
    echo "Waiting for bmitune.sh to complete..."
    sleep 2
  done
}

#Stop stream
adbStop() {
  stop="input keyevent KEYCODE_HOME"

  $adbTarget shell $stop; sleep 2
  echo "Streaming stopped for $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() {
  bmituneDone
  adbStop
  adbSleep
}

main

Fire away on any questions you have after looking these over. :slight_smile:

This page is not required, or available, in the Docker version.

@Gregg_K It's possible you can get by with some very simple scripts. Mainly what you need to do is:

  • Wake the stick and open the app (prebmitune.sh)
  • Be able to change channels (bmitune.sh)
  • Stop the streaming and put the stick to sleep (stopbmitune.sh)

Anything else is just addressing specific app behavior in the interest of making things bulletproof. It's a bummer to have a failed recording, so the extra functions you see address issues I found early on with the DirecTV app that led to failures. It's pretty darn bulletproof now.

Perfect, thank you. I'll start working on getting the npo app to start up and play one of the 3 live channels it has :slight_smile:

New bnhf/ah4c:latest pushed with some minor changes:

  • The main ah4c web page has several links removed to pages that don't apply to this Docker version
  • The "example" scripts are no longer the original scripts/onn/youtubetv, but are now scripts/firetv/directv
  • Any M3U available on the repo can now be found in your local M3U directory
  • scripts/firetv/directv and scripts/firetv/dtvstream have been synchronized
  • Whenever new stable :latest versions are pushed, there will be a corresponding dated version in the form :year.mo.dy, to allow for rolling back to the previous version if there's an issue with :latest
1 Like

Is there an ADB command kind of like the linux tail command to see what's going on while you are opening an app and going through the apps menu etc?

Not exactly, but you could try logcat | grep <your.package.name>

I have a general question about this. Does it pass 5.1 audio and 4k to CDVR ???

The transcoders out there only do Stereo.... unless someone knows about some that do 5.1.