Accessing Live TV services from Plex over the Internet


Like many users, I run my Plex server on a server outside of my home network. There are numerous reasons as to why you may choose this, in my case that’s down to my massively asynchronous internet connection speed.

I have recently purchased an HDHomeRun which I would still like to use with my existing Plex setup. This device is not placed within a datacentre but within my own home.

There are of course negatives to this, it does mean that my live TV service has to do an internet round-trip, however, with my connection, my only concern is the uplink throughput.

As I worked on this, intending to run HDHomeRun, it became apparent that there may be better options. This post is structured in that way in the hope that some useful data exists about my decisions for others.

Throughput of HDHomeRun Streams

One area which was difficult to find good data on before I purchased an HDHomeRun was the network throughput of each stream. This is a crucial piece of information for me.

Considering the HDHomeRun CONNECT with 4 tuners only has a 100 Mbps network interface, it was obvious that the streams had to be on average below 25 Mbps. But information more detailed than this was not so easy to come by.

I collected data for a football match on ITV and ITV HD to understand just what we’re dealing with.

Single Stream Throughput Statistics on the HDHomeRun CONNECT

The data is collected over three hours on the same channel in SD and HD. The channels deviate slightly, but roughly the content is the same.

  HD SD
Mean Throughput 5.59 Mbps 2.35 Mbps
Peak Throughput 17.71 Mbps 5.39 Mbps
Min Throughput 1.24 Mbps 1.22 Mbps
Mean throughput over time on HD and SD streams

What may seem interesting about this data, is that you can identify when the match is live, there are two clear 45 minutes periods where the throughput is far more stable. This is an entirely expected pattern. The point which I want to make clear here is that the content itself will have the largest impact on the required amount of bandwidth, though this data should give you some indication of the expected ranges.

I have concluded from this data, that continuing to run the streams from home is not going to be an issue. The naturally will depend on your connection, but I hope you find this data valuable.

What Services Run?

Initially, it was a case of determining what services run on the HDHomeRun box. This process turned out to be quite simple thanks to the Plex server logs and HDHomeRun documentation.

2 ports listening on the device which we care about

  • HTTP/80, the HTTP API. This is used to fetch channel lineup data.
  • HTTP/5004, the stream port. Simply the streams of each lineup.

As you can probably guess, the HTTP API returns a bunch of addresses to the stream port.

Thanks to Nginx, we can run a proxy to rewrite the URIs returned by the HTTP API to something that’s not the local address provided to the HDHomeRun by DHCP.

The network which hosts your HDHomeRun will need a device to proxy requests and rewrite requests.

Route for remote server to connect to HDHomeRun

You then have two options of how to expose this service to the remote server. Either you connect both of the servers (the one hosting your Plex, and the one hosting your Nginx proxy) to the same VPN network, or you can expose the Nginx server to the web and deny traffic from everywhere except the known source of your Plex server.

I opted for the former, using Zerotier. If you’re not familiar with Zerotier, it’s a decentralised private network. Effectively allowing you to connect arbitrary internet-connected devices to the same network without much configuration. Instead of controlling who can access the Nginx server running, I assume any user on the network has access to the HDHomeRun. For most, this is a safe assumption.

Setting up in a Docker Container

The setup is pretty underwhelming, we just need a very simply Nginx container and a very simple configuration.

The only changes you need to make, are within the docker-compose.yaml.

docker-compose.yaml (change this file on lines with comments how you see fit):

version: "2.1"
services:
  web:
    image: nginx
    volumes:
     - ./nginx.conf:/etc/nginx/templates/default.conf.template
    ports:
     - "8181:80"              # The port for the HTTP API proxy
     - "8182:81"              # The port for the stream proxy
    environment:
      HDHRIP: "192.168.1.118" # The IP address of the HDHomeRun device
      ZTIP: "10.147.18.143"   # The Nginx server's Zerotier IP address
      WEBPORT: "8181"         # HTTP API port (defined above)
      STREAMPORT: "8182"      # Stream port (defined above)

nginx.conf (you do not have to change anything within this file):

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    location ~ \.json$ {
        sub_filter_types text/html application/json;
        sub_filter "$HDHRIP:5004" "$ZTIP:$STREAMPORT";
        sub_filter "$HDHRIP:80" "$ZTIP:$WEBPORT";
        sub_filter_once off;
        proxy_pass http://$HDHRIP;
    }

    location / {
        proxy_pass http://$HDHRIP;
    }
}

server {
    listen 81 default_server;
    listen [::]:81 default_server;
    server_name _;

    location / {
        proxy_pass http://$HDHRIP:5004;
    }
}

That’s pretty much it, run docker-compose up -d and you’re done.

If you make a web request to http://<ip of nginx>:<port for HTTP API>/lineup.json, you should see a bunch of channels, all of which now have your Zerotier IP rather than the IP provided by HDHomeRun.

On the other end now, the Plex box, you can address the same endpoint using the Zerotier IP address. And this is what you need to input into Plex (10.0.0.10:8181 for example).

That’s pretty much it!

Tuner Count Quirks

Now we’re all set up, I started to discover some quirks to the HDHomeRun which I did not initially expect.

Firstly, say you have four tuners in your HDHomeRun and you wish to watch the same channel on more than one device. How many tuners do you occupy? Annoyingly, one for each stream, despite it being technically possible to even stream different channels on the same mux (frequency) using a single tuner.

Now, HDHomeRun probably did this to avoid confusion, maybe to avoid the network interface limitations too. Who knows? But this is misleading to me, it’s not “4 tuners”, it’s “4 streams”.

Is This Even What I Want?

At this point, everything has been pretty simple. The HTTP API is basic enough you can rewrite it on the fly (props to HDHomeRun here, so many needlessly complex APIs exist for home services).

The discovery of TV channels “just works”, nothing is confusing with the initial setup of the device.

But, I have had to run a service off of the device, on the same network, to achieve what I ultimately wanted. It’s fair to say that my use case isn’t typical, however, it is surely not an unheard of requirement.

Chances are, if you’re technical enough to follow everything written here, then there are probably better and more affordable options for you.

So that brings me on to my next steps.

How Else Can We Do This?

For some time, I have known about the existence (and even tried to use, but with little luck) of Tvheadend. Tvheadend is a TV streaming server for Linux and BSD, it’s well supported and pretty much any tuner that works on Linux will work on Tvheadend.

Along with Tvheadend, I also know that the Xbox One TV tuners are extremely well supported on Linux. There was a period a few years ago where these were sold at an RRP of 5 GBP.

With a combination of a server running Tvheadend and an Xbox one TV tuner, we can set up a TV streaming server.

In addition to this, because of the extremely simple API presented by HDHomeRun devices, it’s very easy to replicate this API to present channels from Tvheadend. Fortunately, a nice little project also exists to do this. Antennas.

HDHomeRun have done a great job of getting their API supported by many 3rd party home theatre applications. The fact that the API is so simple means that as we can easily replicate that API, we gain all those integrations on Tvheadend.

Architecture

With these projects in mind, the architecture shifts a little bit.

We still have a server running on the home network, however now, this no longer runs Nginx, it’s just running Tvheadend (this needs to be routable from your remote instance, again, we’ll use Zerotier but you may choose to just open the Tvheadend port).

The remote server only needs to run Antennas. The Plex service can then directly address the locally running Antennas instance.

Antennas will not proxy streams, instead, it provides a manifest of channels and how to address them. Plex itself will get a direct address to Tvheadend and connections will go directly there.

Tvheadend and Antennas remote architecture

The streams provided by both HDHomeRun and Tvheadend are near identical, therefore we can safely assume that the throughput will be the same for the two and we do not need to run tests again.

Docker Manifests

The local server’s manifest will be very simple, we only need to define one service, Tvheadend. This service will only need to be given access to the DVB devices along with a couple of volumes for both its state, as well as any recordings you may make with its DVR feature.

Local server’s docker-compose.yaml

version: "2.1"
services:
  tvheadend:
    image: ghcr.io/linuxserver/tvheadend
    container_name: tvheadend
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
    volumes:
      - ./data:/config
      - ./recordings:/recordings
    ports:
      - 9981:9981
      - 9982:9982
    devices:
      - /dev/dvb:/dev/dvb
    restart: unless-stopped

For the remote manifest, I am assuming you’re going to place it with your existing manifest for your media server. That way, you can address Antennas without exposing a port by its service name (this is the name you can see in the ANTENNAS_URL environment).

Remote server’s docker-compose.yaml

  antennas:
    image: thejf/antennas
    container_name: antennas
    environment:
      - ANTENNAS_URL=http://antennas:5004
      - TVHEADEND_URL=http://10.147.18.143:9981 # The Zerotier address of Tvheadend
      - TUNER_COUNT=1
    restart: unless-stopped

Once these services are running happily, you can add the Live TV/DVR to Plex with the address http://antennas:5004. That’s all.

The Hardware

My setup is made up of the following items:

  • A Dell OptiPlex 3040m. These are tiny, powerful, and silent computers that I love for homeservers. You can find these on eBay for between 100-200 GBP with an SSD and i3 CPU.
  • 5x Xbox One Digital TV Tuners. Extremely cheap and well supported DVB-T tuners, these are 10 GBP (right now) on Amazon. Personally I purchased 3 of these on eBay (new) for 4 GBP each after making an offer. There was a period where the RRP was was 5 GBP.
  • Antenna amplifier and splitter. Nothing too interesting to say here. All the TV tuners attach to this. By purchasing this, I did notice the signal strengh increased a fair amount.
  • Powered USB hub. I am not sure if this was a requirement, however as I needed a USB hub regardless, I thought it was best to get one that’s mains powered.

Conclusions

It quickly became apparent that HDHomeRun was not anything special as I was first led to believe. Others may be in the same position as I was and are considering their options, in which case I hope this information is helpful.

A point that quickly became apparent is that because you already have a server proxying HDHomeRun, you might as well use that server for a service that is a bit more involved. The overhead of running Tvheadend is marginal, most processing happens on the tuners themselves (obviously excluding the effort required from your Plex instance, but this needs to happen in both scenarios).

With Tvheadend, you can use one tuner to stream multiple channels from the same mux. On HDHomeRun on the other hand, a stream (even if the same channel) will occupy an entire tuner. This is probably to avoid confusion, however, it’s an unnecessary restriction from my perspective.

There are of course some negatives to doing this yourself. Primarily is the physical neatness. With the HDHomeRun, you only need to attach power, network, and the aerial. On top of this, there’s the server you have running the Nginx proxy.

The alternative is the server, followed by a USB (probably powered) hub, aerial cables and all the splitters necessary to each of the tuners. This is far messier than it may sound.

Tvheadend is quite a lot more complex to set up than HDHomerun is.

Regardless of which you prefer to use, both options are complete here.

Lastly, in this blog I have mentioned Plex a lot. However everything here is also known to work in Emby/Jellyfin.