A custom media application where the catalog, interface, delivery path, and player were designed as one system.

I built it around a simple rule: the filesystem decides what exists. Media lives on disk; one ingest tool reconciles that library into structured JSON catalogs; PHP and JavaScript turn those catalogs into a responsive browsing interface; and authenticated endpoints deliver the media and every supporting asset.

That gives me a straight line from a file on disk to the card in the browser and the exact byte range reaching the player. Every layer is visible, understandable, and mine to maintain.

Catalog ingest built for safe changes

The ingest process does more than scan folders. It preserves existing metadata, enriches new titles from OMDb when available, reconciles artwork, builds category manifests and playlists, and prunes generated files whose source media no longer exists. It runs as a dry run by default, showing additions, updates, and removals before it changes anything.

The dangerous cases are designed to fail closed. Before reconciliation, the tool checks the media root and validates bind-mounted libraries with sentinel files and device IDs. A commit snapshots the JSON tree first, and a deletion circuit breaker protects catalog manifests and hand-authored skip data unless the removal is explicitly forced. A dropped mount cannot quietly turn into a mass catalog deletion.

A narrow, protected delivery path

The session boundary covers more than the PHP pages. Catalog JSON, posters, JavaScript, CSS, icons, fonts, and raw media all pass through authenticated routes. Caddy directs each protected path to a small PHP endpoint, where resolved files are constrained to their expected roots.

Media is streamed in chunks with HTTP byte-range responses, so the browser can seek without exposing absolute filesystem paths or loading an entire file into memory. Play history uses locked access and atomic replacement rather than introducing a database for one small piece of runtime state.

An interface shaped by the library

The interface follows the content instead of forcing every collection into one view. Films and series use metadata-rich cards with era, recency, and subcategory filters. Music and long-form audio use a recursive collection browser with no hard-coded depth. Series open into season and episode views, while lightweight browser state remembers filters, last-opened items, active playlist tracks, and playback position.

A player shaped by real browser failures

Audio and video share one custom player with playlists, auto-advance, saved position, recent history, keyboard controls, fullscreen video, and optional per-series intro and outro skipping.

Writing the controls was the easy part. The deeper work came from long variable-bitrate audio, interrupted mobile streams, and iOS restoring a media element in a state where the interface appears to seek while playback continues from zero. The player treats that as a recoverable failure: it verifies that every requested seek actually landed, reloads a wedged element in place, waits for the target to enter a valid seekable range, restores the position, and resumes only when it is safe. Pending recovery is cancelled the moment the user takes control.

How it stays operable

The application runs on a security-hardened platform I host from home on a repurposed ThinkPad. Cloudflare and Caddy carry requests from the public edge into an isolated PHP-FPM pool; encrypted storage holds the library; and the firewall exposes only the services the platform actually uses.

Code moves from the authoring tree into a receive-only production folder through Syncthing, while automated Git history records the deployed tree outside the webroot. Ingest runs beside the mounted library, then returns its generated catalogs and artwork to the authoring path so production does not become a second, hidden source of truth.

What this demonstrates

  • End-to-end application engineering across ingest, PHP rendering, browser state, protected delivery, and playback
  • Safety-conscious filesystem reconciliation using dry runs, mount validation, deletion circuit breakers, snapshots, and atomic writes
  • Authenticated, path-constrained media delivery with HTTP byte-range support
  • Interface architecture for metadata catalogs, recursive collections, series navigation, and persistent playback state
  • Playback resilience built from observed iOS and WebKit failure modes, including verified seeks and in-place recovery
  • Home-hosted application operations across Cloudflare, Caddy, PHP-FPM, encrypted storage, synchronized deployment, monitoring, and recovery