Skip to content
Shiny.NET
Shiny MAUI Shell v7 - App Links, App Shortcuts, & Navigation Interception!Shortcut me to it

tvOS

Frameworks
.NET
Operating Systems
tvOS

An Apple TV is a device on the network that is always plugged in, always on the same LAN, and has no browser and no address bar. That combination makes it an unusually good place to put a small server and an unusually bad place to debug one, which is most of what this page is about.

The core server needs nothing special. Shiny.Net.HttpServer targets plain net10.0, so a tvOS app references it the way any other project does, and it compiles against the tvOS platform surface with the trim and AOT analyzers on and no platform-compatibility warnings at all.

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-tvos</TargetFramework>
<SupportedOSPlatformVersion>17.0</SupportedOSPlatformVersion>
<OutputType>Exe</OutputType>
<ApplicationId>com.example.myapp</ApplicationId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Shiny.Net.HttpServer" />
</ItemGroup>
</Project>

The Info.plist entry that decides whether any of this works

Section titled “The Info.plist entry that decides whether any of this works”
<key>NSLocalNetworkUsageDescription</key>
<string>Serves this app's status page to other devices on your network.</string>

tvOS 16+ gates the local network exactly as iOS 14+ does, and it gates serving on it as much as connecting out. Without the key the listener binds, the server reports itself as Running, and no other device can reach it — with nothing in the log to say why.

This is worse on a TV than on a phone. On a phone you can open the URL in the device’s own browser to see whether the problem is the server or the network. An Apple TV has no browser, so the only witness is the very device that cannot connect.

tvOS suspends a backgrounded app exactly as iOS does, and no background mode legitimately holds a listener open on either. So the useful behaviour is not “keep serving” — that is not on offer — but to be serving again by the time anyone is looking at the app.

Wire it to the app delegate:

[Register(nameof(AppDelegate))]
public class AppDelegate : UIApplicationDelegate
{
HttpServer? server;
public override bool FinishedLaunching(UIApplication app, NSDictionary? options)
{
var builder = HttpServer.CreateBuilder();
builder.Configure(o =>
{
o.Address = IPAddress.Any; // the point is to be reachable from other devices
o.Port = 8080;
});
this.server = builder.Build();
this.server.MapGet("/ping", _ => Results.Text("pong"));
return true;
}
// A cold start goes through OnActivated but not WillEnterForeground, so both are wired.
public override void WillEnterForeground(UIApplication app) => _ = this.server!.StartAsync();
public override void OnActivated(UIApplication app)
{
if (this.server is { IsRunning: false } s)
_ = s.StartAsync();
}
public override void DidEnterBackground(UIApplication app) => _ = this.server!.StopAsync();
}

Shiny.Net.HttpServer.Mobile does this and more — a bounded retry for the bind a half-woken network refuses, and a rebind when the device changes network. It does not target tvOS yet because Shiny.Core does not; when that lands, the overrides above become a single AddHttpServerLifecycle() call.

cloudflared, ngrok, Tailscale agents No. They work by starting a separate binary, and tvOS forbids process creation. The agent now throws a TunnelAgentException saying so rather than failing from inside the BCL.
SSH tunnels and Azure Relay Yes. Pure managed code, no agent to spawn.
Microsoft.Extensions.Logging.Console No — and it fails at native link time, not at runtime: it P/Invokes GetStdHandle and GetConsoleMode, and clang stops with “Undefined symbols for architecture arm64” long after the C# compiled clean. A TV app has no console anyway.
Durable file storage Careful. A tvOS app gets a read-only bundle and a purgeable Caches directory; there is no Documents directory that survives. Serve static files from the bundle or a ZipFileSource, and treat anything written to disk as something the OS may reclaim between launches.

tvOS is fully ahead-of-time compiled with no JIT anywhere, so nothing can fall back to reflection at runtime. That makes the source-generated typed endpoints the recommended tier here rather than merely the fastest one, and the same applies to JSON: use a JsonSerializerContext rather than reflection-based serialization.

samples/Sample.TvOS in the repository is a complete, minimal version of this page: a native tvOS app that starts the server on IPAddress.Any when it foregrounds, stops it when it backgrounds, and puts the LAN URL on the TV screen so there is something to type on another device.