IMPLEMENTATION

One local server. One published protocol.

The server delivers the browser interface and API from the same local address while each TV keeps a durable manifest, verified media, and future playback transitions.

ADMINISTRATION

React + TypeScript

npm ci
npm run build:admin
dotnet run --project server/LessonCue.Server

The local browser experience manages accounts, classes, dated lessons, playlists, media previews, screens, a phone controller, pre-roll, countdowns, settings, and audited bulk planning and media operations.

LOCAL SERVER

ASP.NET Core 10 + SQLite

dotnet run --project server/LessonCue.Server
dotnet test server/LessonCue.Server.Tests

Authentication, discovery, pairing, manifests, ordered playback commands, media ranges, status, audit events, and SignalR invalidation live behind /api/v1.

OPTIONAL REMOTE ACCESS

Restricted Cloudflare Tunnel

A protected root helper installs a checksum-verified connector; a separate unprivileged systemd service reads a root-owned token file and reports localhost-only edge metrics. The credential never returns through the browser API.

ANDROID / FIRE TV

Kotlin + Compose for TV + Media3

gradle -p android-tv :app:testDebugUnitTest :app:assembleDebug

The native app implements remote focus, pairing, local identity, background caching, browser-controller commands, and ExoPlayer playback.

APPLE TV

SwiftUI + AVKit

cd tvos
xcodegen generate
open LessonCueTV.xcodeproj

The independent tvOS client implements Bonjour, Keychain credentials, AVPlayer, browser-controller commands, and verified offline cache.

LOCAL COMMAND CHANNEL

Phone browser → server → paired TV

Authenticated phone controls are versioned and queued in SQLite. TV clients establish a startup cursor, poll with their device credential, and consume play, pause, resume, previous, next, stop, and seek commands in order.

SCHEDULE SEMANTICS

Pre-roll → countdown → lesson

At designatedStartAt - durationMs, the client leaves the repeating pre-roll and starts the countdown. If it wakes midway, it seeks by elapsed time so the countdown still finishes exactly at the designated start.

Full developer guide ↗