Tenjin

A media server that is yours: own libraries, own user management, optional relay for access from anywhere.

Version 0.1.0 · In development

Windows · macOS · Linux · Android · Android TV · Fire TV

Tenjin desktop client: start page with continue-watching row and libraries

Three components, three paths

Tenjin consists of the media server, the optional relay server and the clients, all in C++ with Qt. A client reaches a server over one of three paths; the relay is only needed for B and C. Any number of servers can register with one relay, and a server with several relays.

tenjin-client Desktop, phone, tablet, TV native player, local proxy tenjin-relay public, Let's Encrypt certificate verifies tokens offline tenjin-server libraries, users, tokens own Ed25519 key pair Path A · direct · QUIC/TLS 1.3 · end-to-end Path B · relay passthrough (default) relay only forwards encrypted packets Path C · relay termination · only when enabled by the admin TLS client ↔ relay and relay ↔ server, caching possible outbound control channel, no port forwarding A: direct, relay sees nothing B: passed through, relay sees only packets C: terminated, relay sees contents
Three components, three connection paths · component diagramOpen full size
Path When Encryption Relay sees contents
A · Direct LAN, server reachable from outside (port forwarding, UPnP) or successful hole punching QUIC/TLS 1.3, end-to-end no
B · Relay passthrough (default) Direct path fails QUIC/TLS 1.3 end-to-end, the relay only forwards (TURN principle) no
C · Relay termination Enabled by the relay admin, e.g. for a cache TLS client ↔ relay and relay ↔ server, sign-in with a signed token yes

The client tries path A first (local addresses, then public ones) while hole punching starts in parallel via the relay. If nothing succeeds within a window of about three seconds, it falls back to B or C.

Transport

  • QUIC (HTTP/3) with TLS 1.3 for control and streaming, libraries ngtcp2 and nghttp3, TLS via OpenSSL 3.5 or newer; fallback to TCP with TLS 1.3 (HTTP/2) if UDP is blocked. The relay does not terminate QUIC and gets by with OpenSSL 3.0.
  • Packet size: UDP payload 1,350 bytes by default so VPN and tunnel headers fit; the administrator can switch to dynamic path MTU discovery up to 1,452 bytes or a fixed size between 1,200 and 1,452 bytes.
  • NAT: address candidates are brokered ICE-style via the relay; against NATs that assign a new port per destination there is a port search with many client sockets.
  • Network change: the client detects a change by its addresses, drops the QUIC connection and rebuilds the path (direct, hole punching, relay). A silently broken path counts as dead after eight seconds without a packet.

Media server modules

The media server is a background service made of modules that communicate through interfaces and can be tested individually.

Module Task
Core / service host Start as Windows service, daemon or container, configuration, logging
Net QUIC/HTTP-3 server, API routing, relay control channel, mDNS
Auth Accounts, permissions, tokens, devices, account linking
Storage Uniform file system abstraction over local and remote sources
Library Scanner, folder structure recognition, mapping to metadata
Metadata Provider chain, rate limiting, image cache, search
Analysis Preview images, chapters, intro/outro, loudness
Streaming Playback decision, HLS packaging, transcode sessions
Scheduler Scheduled and manual tasks with progress
Sync Synchronisation of favourites and progress via the relay
Plugins Loading, versioning, isolating extensions
Telemetry Activities, load metrics, stream details for administration

Concurrency and processes: separate thread pools for I/O, CPU work and network; SQLite in WAL mode with exactly one writer thread and several read connections. Everything that decodes media files runs in separate processes (media worker, transcoding), so a broken file or a driver bug never takes the server down.

Storage sources

Contents live locally or on shares in your own network; cloud storage is not supported. Polling only compares directory listings and never reads file contents; the interval adapts.

Source Access Change detection
Local direct Real time: ReadDirectoryChangesW, inotify, FSEvents
SMB UNC path, libsmb2 or mount SMB2 change notify, otherwise polling
NFS Mount or libnfs Polling
WebDAV HTTP(S) PROPFIND with ETag and Last-Modified

Playback decision

Direct play. The file is streamed unchanged if container, codecs and tracks match the device's capabilities and the bitrate stays below the effective limit.

Remux. Only the container is repackaged, video and audio stay untouched; for instance when the device supports the codec but not the container.

Transcode. Only when necessary: re-encode the video or audio track, with hardware acceleration (NVIDIA, Intel, AMD) and selectable quality levels.

The effective limit is the minimum of device capability, user limit, device limit and client setting; the upload balancer distributes internet bandwidth across external streams.

Metadata

Per media type a global order of providers applies (TMDB, OMDb, …); if the first finds nothing, the next is asked. Rate limits apply per provider instead of globally. Texts and IDs live in SQLite, images either in the metadata folder or next to the media file following the Kodi/Jellyfin convention (poster.jpg, fanart.jpg, season01-poster.jpg), so other programs find them too. Existing trickplay images from Jellyfin are reused directly.

Client

  • client-core · Qt-free logic: connection setup, session, playback decision, models
  • ViewModels · state and actions of the pages, without Qt Quick
  • QML per form factor · desktop (sidebar), tablet (navigation rail), phone (bottom bar, landscape), TV (ten-foot, focus)
  • Player · libmpv (desktop), Media3/ExoPlayer (Android), AVPlayer (Apple)
  • local proxy · the player fetches the stream through a local proxy of the client that handles TLS and tokens

One client program covers all form factors; the image cache stores only versioned addresses (?v=<updated_at>), which the server delivers as immutable.

Operation

  • Windows 10/11 and Windows Server: media server as a service; SMB sources as UNC paths with stored credentials, because services do not see drive letters.
  • Linux: primarily as Docker containers (tenjin-server, tenjin-relay, based on Debian 13), alternatively systemd; OpenSSL 3.5 or newer is required for server and app.
  • macOS and QNAP (QTS): planned as agent and daemon respectively.
  • Clients: Windows, macOS, Linux; Android 9 and newer for phone, tablet, Android TV; Fire TV with a 32-bit package.

Test strategy

Unit

Core logic in UI-free libraries with unit tests; Qt Quick only for presentation.

Integration

Server, relay and client working together, import from Plex and Jellyfin with a dry run.

UI tests

265 user interface tests (ctest -L ui) across all form factors, with a preview harness for desktop, phone, landscape and TV.

Devices

Nvidia Shield, Fire TV Cube and phones on the network; stutter is measured with dumpsys SurfaceFlinger.