Documentation
How Kinode Media Server works, how to run it, and what to do when it misbehaves.
What this is
Kinode Media Server serves the video and music already on your own disks to the devices you already own. It runs on one machine in your house. Everything else, meaning phones, TVs, browsers and consoles, connects to that machine.
There is no subscription, and no feature is held back for a paid tier. Remote streaming, hardware transcoding, subtitle handling and library sharing are all included because there is nothing to sell you.
The pieces
| Piece | Runs where | What it does |
|---|---|---|
| The server | Your machine | Reads your library, serves the web app, streams and converts video and audio. |
| The hub | usekinode.app | An account system and a directory. It helps apps find your server when you are away from home. It never touches your video. |
| Apps | Phones, TVs | Android and Roku clients. They talk to your server directly whenever they can. |
| The Discord bot | Your machine, optional | Announcements, library search, and music in voice channels. |
The hub stores your account, a list of your servers, and their addresses. It does not store what you watch, who your managed users are, or anything about your files. When an app streams video, it connects to your server directly and the hub is not in the path. If the hub is offline, playback keeps working.
Two ways it can be set up
The installer produces a bundled install. A complete ffmpeg with every codec compiled in ships inside it, so nothing else has to be present for playback to work.
There is also an attached mode, where the server reads an existing media library database and uses external binaries. That is configured through data/engine.json or the ENGINE_* environment variables. Bundled binaries win when both are present, because the bundled ffmpeg carries codecs that vendor builds leave out.
The scanner that indexes your media folders is part of the attached mode. A bundled install ships with playback and everything around it, and it has no scanner of its own yet. If you install fresh and nothing appears after you add a folder, that is why. See Scan finds nothing for what to do about it today.
Installing
What is inside the installer
Everything the server needs is in the file you download. Nothing is fetched during setup, so a slow or missing internet connection does not break the install.
| Bundled | Why |
|---|---|
| Node runtime | The server is written in JavaScript. The runtime ships alongside it so you never install one yourself, and a system Node upgrade cannot break the server. |
| ffmpeg, full build | Every common codec is compiled in. This is what lets odd files play instead of failing. Vendor ffmpeg builds often ship without the video codecs for licensing reasons, which is exactly the problem this avoids. |
| Service wrapper | Turns the server into a Windows service so it starts with the machine and restarts if it stops. |
| The Discord bot | Optional. You choose during setup whether to include it. |
The unknown publisher warning
Windows will show a blue SmartScreen box saying it protected your PC and naming an unknown publisher. Choose More info, then Run anyway.
That warning is about a code signing certificate, which costs a few hundred dollars a year from a certificate authority. Until one is bought, every download shows this. It is not a statement about what the installer does. If that bothers you, the source is public and you can build it yourself.
Ports
| Port | Protocol | Used for |
|---|---|---|
5466 | TCP | The web app and the API. This is the one you open in a browser. |
5467 | UDP | Local discovery, so apps on your network find the server without anyone typing an address. |
5468 | TCP | The Discord bot's local API. Only installed if you chose the bot. |
5466 spells KINO on a phone keypad. The block was chosen to stay clear of Plex, which uses 32400 for its web interface and 32410 through 32414 for discovery. An earlier build of this server used 32402 and 32410, and 32410 collided with Plex discovery on any machine running both. That is fixed, and it is the reason the ports look arbitrary.
You can change the main port during setup. The installer validates it is between 1024 and 65535, writes it into the service definition, and uses it for the shortcuts and the firewall rule.
The firewall rule
If you leave the firewall task ticked, setup runs two netsh commands and creates two inbound rules:
netsh advfirewall firewall add rule name="Kinode Media Server"
dir=in action=allow protocol=TCP localport=5466 profile=private,domain
netsh advfirewall firewall add rule name="Kinode Media Server discovery"
dir=in action=allow protocol=UDP localport=5467 profile=private,domain
Both are scoped to the private and domain profiles. They do not apply to public networks, which is what Windows calls a coffee shop or an untrusted connection.
A firewall rule lets devices on your own network reach the server. Reaching it from outside your house additionally needs a port forward on your router, which is a separate thing you have to do deliberately. See Remote access.
Where your data lives
The program goes in C:\Program Files\Kinode Media Server. Your data goes somewhere else entirely:
C:\ProgramData\Kinode Media Server\
data\
accounts.db accounts, profiles, PINs, watch history, playlists
secret.key signs sign-in tokens
plugins\ installed plugins
trickplay\ seek preview images
subtitles\ downloaded subtitle files
logs\ service logs, rolled at 10 MB, four kept
This split is deliberate. A service account cannot write into Program Files, and keeping data outside the program directory means an upgrade or a reinstall never touches your accounts or your watch history.
The server finds this directory through the KINODE_DATA environment variable, which the installer sets in the service definition. Running from a source checkout with no such variable, the data sits beside the code in data\, which is what you want while developing.
Service or window
Ticking the service task during setup installs a Windows service called Kinode Media Server. It starts automatically after boot, restarts itself ten seconds after a crash, and writes rolling logs.
sc query KinodeMediaServer
net stop KinodeMediaServer
net start KinodeMediaServer
If you skip the service, use the Start Kinode Media Server.cmd shortcut that setup writes into the install folder. It opens a console window and the server runs until you close it. That is useful for testing because you see errors immediately.
Registry entries
Setup writes five values under HKLM\Software\Kinode\MediaServer: InstallPath, DataPath, Version, Port, and Architecture. Uninstalling removes the key. Nothing else in the registry is touched, and no file associations or shell extensions are registered.
Uninstalling
Uninstall from Settings, Apps, or from the Start menu shortcut. It stops the service, removes it, deletes both firewall rules, and removes the program files.
It then asks whether to remove your accounts, watch history and settings. Choosing No keeps C:\ProgramData\Kinode Media Server intact, which is what you want if you are reinstalling or moving to a new version. Your media files are never touched either way, because the server only ever reads them.
Architectures
There are two builds: x64 for Intel and AMD machines, and arm64 for Windows on ARM laptops. The download page detects which one you need and preselects it, and you can override that.
There is no 32 bit build. Windows 11 has no 32 bit edition, a 32 bit process cannot address more than 4 GB of memory, and video transcoding wants more than that. If you have a real need for one, say so on the forum.
First run
Creating the first account
Open http://localhost:5466. The first account you create becomes the owner of that server, with administrator rights on it. Nothing is emailed and no account is created anywhere else. This account exists only on your machine.
People who run their own server are called owners. The word admin is reserved for staff on this website, meaning the people who moderate the forum and review plugins. The two are unrelated, and owning a server gives you no rights here.
Adding libraries
Go to Settings, then Library, and add a folder. Point it at the directory that holds your media. Give it the right type, because it changes how things are grouped and displayed:
- Movies expects one film per folder or per file.
- TV Shows expects show, then season, then episode.
- Music expects artist, then album, then track.
Naming that scans cleanly
Scanners match on filenames. These layouts work:
Movies\
Blade Runner (1982)\Blade Runner (1982).mkv
Heat (1995)\Heat (1995) Bluray-1080p.mkv
TV Shows\
The Wire\Season 01\The Wire - S01E01 - The Target.mkv
Music\
Radiohead\OK Computer\01 Airbag.flac
A year in parentheses removes almost all ambiguity for films, especially remakes. S01E01 is the most reliably parsed episode format.
The scan
A scan walks the folder, works out what each file is, and writes it into the library database. Artwork and descriptions are fetched afterwards, so titles usually appear before their posters do.
Large libraries take a while. Ten thousand files is normal at tens of minutes, and it is disk bound rather than processor bound.
Libraries also rescan themselves once a day at 3 AM, so files you drop in overnight show up without anyone pressing anything.
A bundled install has no scanner binary configured, and a scan will fail immediately rather than slowly. This is a real gap in the current build. The troubleshooting entry explains the workaround.
Artwork and descriptions
Posters, backgrounds and summaries are fetched after the files are indexed. If a film has the wrong poster it is almost always because the filename matched the wrong title. Renaming the file to include the year and rescanning fixes it.
Profiles and family
Managed users
A managed user is a person on your server who has a display name and nothing else. They have no password and no email address. After you sign in, a screen asks who is watching, and everyone picks their name.
What each managed user gets:
- Their own watch history and Continue Watching row.
- Their own ratings, watchlist, playlists and recommendations.
- Access to exactly the libraries you granted them.
- An optional PIN, between four and eight digits, stored hashed.
Create them from the who-is-watching screen using Manage users. The account owner is always the first tile and cannot be deleted.
They are stored only in your server's database. The hub has no concept of them, no table for them, and no way to learn they exist. Nothing about a managed user ever leaves your machine.
PINs
A PIN gates entering a profile. It is not a password and it does not protect the account, because anyone who is already signed in is inside your server. It stops a child from picking your profile and it stops someone seeing your Continue Watching row.
PINs are stored as bcrypt hashes. There is no way to read one back, including for you. Clear it and set a new one from the same edit screen.
Per-profile library access
Editing a managed user offers either everything on the server, or a specific list of libraries. Access is enforced on the server for every request, so a hidden library cannot be reached by guessing a URL.
A profile can only ever be narrowed, never widened past what the account itself may see. If someone shared a subset of their libraries with you, your own managed users can never see more than that subset.
Kid mode and rating limits
Kid mode gives that profile a simplified interface with larger tiles, and hides settings and statistics.
A rating ceiling blocks anything above it. Ratings are ranked, and unrated content is blocked whenever a ceiling is set, because unrated is where the surprises live.
| Rank | Ratings treated as equivalent |
|---|---|
| 1 | G, TV-Y, TV-G |
| 2 | PG, TV-Y7 |
| 3 | TV-PG |
| 4 | PG-13, TV-14 |
| 5 | R, TV-MA |
| 6 | NC-17, X |
Daily limits and bedtimes
A managed profile can have a daily watch limit in minutes and a bedtime window. Both are enforced by the server on every playback request, so switching device or app does not get around them.
The daily limit counts real time watched, taken from the same ledger that feeds the statistics page. It is not the sum of runtimes, so abandoning a film after ten minutes costs ten minutes.
How bedtime handles midnight
A bedtime window is a start time and an end time. When the start is earlier than the end, the window is the obvious range. When the start is later than the end, the window wraps past midnight.
bedtime 20:00 to 22:00
blocked between 8 PM and 10 PM, same evening
bedtime 21:00 to 07:00
start is after end, so the window wraps:
blocked from 9 PM through midnight and on until 7 AM
In code that is a single comparison. When start is less than or equal to end, the current time must fall inside the pair. Otherwise the current time must be at or after the start, or before the end. The second case is what makes an overnight window work.
Bonus time
The account owner can grant extra minutes on top of today's limit from the profile edit screen. Bonus minutes are recorded against today's date and are ignored tomorrow, so there is nothing to remember to undo.
What happens when a limit is reached
Playback that has already started is stopped at the next progress report, which is within about ten seconds, and the reason appears on screen. Starting something new is refused immediately with the same message. The watch time is still recorded, because the ledger is written before the check runs.
Playback
Direct play, direct stream, and converting
Every time you press play the server decides how to deliver the file. The player shows which one it chose, and Settings shows why in plain words.
| Mode | What happens | Cost |
|---|---|---|
| Direct play | The original file is sent untouched. | Almost none. This is the goal. |
| Direct stream | Video is copied as-is, audio and the container are rebuilt. | Small. Video is the expensive part and it is not being re-encoded. |
| Converting | Video is decoded and re-encoded. | Large. This is what makes a processor work hard. |
What qualifies for direct play
All three of these have to be true:
| Part | Accepted |
|---|---|
| Video codec | h264, vp8, vp9, av1. Also hevc when the browser proved it can decode it. |
| Audio codec | aac, mp3, opus, vorbis, flac |
| Container | mp4, mkv, webm, mov, m4v |
If the video codec is fine but something else is not, the result is direct stream. If the video codec itself is unsupported, the result is converting.
Chrome plays a file with DTS or E-AC3 audio silently. The video runs, the audio track is simply missing, and it looks like a broken speaker rather than a codec problem. Treating those as unsupported forces an audio rebuild, which costs very little and means sound always works.
Picking a quality forces conversion
Choosing 1080p, 720p or 480p in the player always converts, because resizing a picture requires decoding it. The presets are 1920 wide at 8 Mbps, 1280 at 4 Mbps, and 854 at 2 Mbps.
HEVC and 4K
The browser tests whether it can hardware decode HEVC and tells the server. If it can, a 4K HEVC file is stream copied instead of re-encoded. On an 80 GB remux that is the difference between a modest network transfer and a processor at full load for two hours.
Copied HEVC is tagged hvc1 inside the output container, because browsers reject the alternative tag.
The fallback that makes odd files work
If a direct or direct stream playback errors part way through, the player restarts the same moment as a full conversion and shows a short message. This catches 10 bit H.264, damaged containers, and files whose metadata claims a codec they do not actually contain.
The practical effect is that if ffmpeg can decode it, it plays. Conversion also forces 8 bit output, deinterlaces frames that are flagged as interlaced, and allows a large probe window so unusual Matroska files are read correctly.
Audio tracks
Files with more than one audio track show a track list in the player's settings menu. Picking a track that is not the first one forces at least a direct stream, because browsers cannot switch audio tracks by themselves.
Subtitles
Three sources are used, in this order:
- Embedded text tracks inside the file. Recognised formats are SRT, SubRip, ASS, SSA, mov_text, WebVTT and VTT.
- Sidecar files next to the media file, with extensions
.srt,.ass,.ssa,.vttor.sub. - Downloaded from OpenSubtitles, if you set an API key in Settings.
PGS and VobSub are pictures rather than text. They cannot be turned into browser subtitles without optical character recognition, so they are not listed. Use a sidecar file or generate one.
Generated subtitles
Movies and episodes have a Generate subtitles button. It extracts a small mono audio file, sends it to a Kinode Transcripts server, and writes the result back as an .srt beside the media file. Configure the engine URL and key under Settings, Maintenance.
With nightly captioning enabled, up to three recent additions that have no subtitles are captioned at 4 AM.
Remembered language
The subtitle track you pick is remembered per show and per profile. The next episode selects the same language without asking.
Seek preview images
Hovering the seek bar shows a frame from that point. A background job builds one image strip per file using keyframes only, one file at a time, and caches it permanently. The first time you hover a file it queues the job, so previews appear on the next play rather than immediately.
Equalizer and normalization
Both live behind the equalizer button in the player and the music bar, and both apply live. Dragging a slider changes the sound immediately with no restart.
- Ten band equalizer from 31 Hz to 16 kHz, plus or minus 12 dB. Presets are Flat, Dialogue, Bass boost, Treble boost and Less bass. You can save your own named curves.
- Audio normalization is a compressor that evens out films where dialogue whispers and explosions do not. One toggle.
Settings are saved per profile. Custom equalizer presets are shared across the profiles on one account.
Accessibility and TV mode
The accessibility panel is reachable from the top bar and from the who-is-watching screen. It offers interface size, subtitle size, a solid subtitle background, high contrast, and reduced motion. The system setting for reduced motion is respected whether or not the toggle is on.
TV mode makes everything larger, adds strong focus outlines, and turns on arrow key navigation across the whole interface with Enter to select. That makes any browser on a television or a console usable from a sofa with a keyboard or a remote.
Music
Music libraries browse as artists, then albums, then tracks. Playback uses a bar along the bottom of the window, so browsing does not interrupt what is playing. Starting a video pauses the music and the reverse is also true.
Files stream in their original format. Browsers handle MP3, FLAC, M4A, OGG and WAV without conversion. Operating system media keys work through the Media Session API, so the play button on a keyboard or a lock screen does what you expect.
Playlists
Playlists are per profile and appear in the sidebar. Add a track from any album with the plus button that appears when you hover a row. A playlist page can play, shuffle, reorder and remove.
Lyrics
Put an .lrc file next to the audio file and the lyrics button scrolls it in time with the music, highlighting the current line. A plain .txt file with the same base name is shown as static text. Nothing is fetched from the internet.
Audiobooks and long audio
Anything longer than fifteen minutes resumes where that profile stopped rather than starting over. The music bar has a speed control that cycles through 1x, 1.25x, 1.5x, 2x and 0.75x.
Volume leveling
Each track gets a loudness measurement the first time it is played, taken in the background. Later plays use it to bring tracks to a common level, so a quiet rip and a loud master do not make you reach for the volume. The toggle is in the accessibility panel.
Radio
The radio button builds an endless queue from any track, seeded by the artist first and then filled from the rest of that library. It uses your own files, and no external service is involved.
Remote access
How the directory works
Your server sends the hub a heartbeat every five minutes. That heartbeat carries four things: a machine identifier, a shared secret, the friendly name, and a list of addresses where the server can be reached.
When an app signs in, the hub returns each server the account owns or has been shared, with a list of candidate addresses and an access token. The app tries the addresses in order and uses whichever answers.
Access tokens are signed with your server's own secret, which the hub holds a copy of from registration. Your server verifies them itself, without asking the hub anything. That is why playback continues to work when the hub is down.
Why local addresses are tried first
The candidate list puts private network addresses ahead of the public one. When your phone is on your own wifi it talks to the server directly across the local network, which is faster and does not touch your internet connection at all. The public address is used only when the local ones do not answer.
Making it reachable from outside
Two things are needed. The firewall rule, which the installer created, and a port forward on your router, which you have to do yourself.
- Give the machine running the server a fixed local address, either a static address or a DHCP reservation on the router.
- In the router's admin page, forward external TCP port 5466 to that machine's port 5466.
- Wait for the next heartbeat, which is within five minutes, or restart the server to send one immediately.
Your server cannot see its own public address from behind a router, so the hub fills it in. The address a heartbeat arrives from is the public address, and the hub records it and hands it back as a candidate.
Setting your own public address
If you have a domain, dynamic DNS, or a reverse proxy with a certificate, put the full address into Settings, Maintenance, Public address. For example https://media.example.com. That takes priority over the address the hub infers, and it is the only way to get HTTPS on remote connections today.
Without your own domain and certificate, remote streaming happens over unencrypted HTTP. That is acceptable inside a household and it is not acceptable for anything sensitive. A reverse proxy with a free certificate fixes it, and setting the public address above is how you tell the server about it.
Apps
The web app
The browser interface is installable. Chrome and Edge offer an install button in the address bar, which gives it a window without browser furniture and its own icon. A small service worker caches the interface so it opens even if the server is briefly unavailable, though playback obviously needs the server.
Android
- Casting. A cast button appears when a Chromecast is on the network. Local playback pauses, the television picks up at the same position, and stopping the cast hands the position back to the phone.
- Downloads. Long press any movie or episode and choose Original, 1080p or 720p. Downloads are stored in app private storage with their artwork, and the Downloads screen plays them with no server involved. Watch progress is queued locally and sent to the server the next time it is reachable.
- Remote. Lists players across every server on the account and drives them with play, pause, seek, next, previous and stop.
Casting uses the converted stream rather than the original file. Chromecast support for Matroska and DTS is inconsistent, so the reliable path is used deliberately.
Roku
The Roku channel is sideloaded rather than published. Enable developer mode on the Roku, browse to its address, and upload the zip.
It has the who-is-watching screen with PIN entry, sends the profile with every request so watch history is correct, and can be driven from the Remote screen in the Android app. Next and previous are ignored on Roku because it has no episode queue yet.
Discord bot
The bot is optional and runs next to the server. It needs a Discord application of your own, because a bot token identifies your bot and cannot be shared.
Commands
| Command | What it does |
|---|---|
/nowplaying | What is streaming right now, with who is watching and whether it is converting. |
/search | Search the library from Discord. |
/stats | Watch time across the household, library size, data served. |
/server | Health checks and drive space. |
/request | Ask for something to be added. Goes into the request queue and triggers automation if it is set up. |
/bug | File a bug on the tracker. |
/bugs | List bugs, optionally filtered by status. |
/play | Play a track, an album or an artist in your voice channel. |
/playlist | Play a playlist from the server. |
/nowqueue | What is lined up. |
/skip /pause /stop | Transport controls. |
/watch | Start a watch party and post the join code. |
/queue /triage /review | Staff commands for reports, bug status and plugin review. |
Channels it creates
Calling POST /bot/setup once creates seven channels and returns their identifiers to paste into the configuration:
| Channel | Carries |
|---|---|
kinode-announcements | New episodes, new arrivals, the weekly recap |
kinode-requests | Requests, and a message when one is ready |
kinode-alerts | Disk space warnings |
kinode-activity | What is playing |
kinode-bugs | Bug intake |
kinode-bug-log | New bug notifications for whoever is fixing them |
kinode-moderation | Reports and plugin reviews |
Bug intake threads
Anything said in the bugs channel becomes a bug on the tracker. The bot replies with the number and opens a thread underneath. From then on that thread and the bug are the same conversation: messages in the thread become comments on the bug, and staff replies on the website are pushed back into the thread.
Severity is guessed from the wording. Words like crash, data loss and cannot sign in read as critical. Broken, error and hangs read as high. Typo, cosmetic and occasionally read as low. You can always override it.
The link between a Discord thread and a bug is held in memory. Restarting the bot means older threads stop mirroring. That is deliberate, because persisting the mapping risks a stale identifier routing a comment onto the wrong bug. New threads work normally.
Music in voice channels
The bot streams from your library into a voice channel. It does not use Lavalink or any other external audio service. The server converts the track to Ogg Opus, which Discord voice plays directly, so there is no Java process and no native audio module to compile.
The player message has album art, a progress bar, and buttons for back, pause, skip, stop, shuffle, loop and queue, plus a radio button that extends the queue with similar music.
Rich Presence
Showing what you are watching on your own Discord profile is a separate program called presence.js, and it has to be. Discord only allows an application to set its own status, so a bot can never write to your profile. Rich Presence works over a local socket that the Discord desktop client opens on your machine, which means the program has to run on your machine next to Discord.
It reads what you are playing from your server, and writes it to your profile with artwork, a live progress bar and a paused state. There is a private mode that keeps it running without showing anything. Nothing leaves your network.
The bot itself shows a server wide status instead, naming what is playing when one thing is, or a count when several are.
Plugins
The shape of a plugin
A plugin is a folder holding a manifest and up to two scripts. Both scripts are optional, so a plugin can add only a home row, only a panel on an item page, only an endpoint, or any combination.
on-this-day\
plugin.json required
server.js optional, runs in the server process
client.js optional, runs in the web app
{
"id": "on-this-day",
"name": "On This Day",
"version": "1.0.0",
"author": "you",
"description": "One or two sentences.",
"homepage": "https://github.com/you/on-this-day",
"permissions": ["library:read", "ui:home", "storage"],
"server": "server.js",
"client": "client.js",
"settings": [
{ "key": "minYears", "label": "Only films this old", "type": "number", "default": 5 }
]
}
The identifier must be lowercase letters, numbers and dashes, and it must match the folder name. Anything listed under settings becomes a form on the owner's plugin page, and whatever they save arrives as plugin.config.
Permissions
A plugin asks for what it needs. The owner sees the list in plain words before enabling it, and a permission that was not requested is simply absent from the object the plugin receives.
| Permission | Grants |
|---|---|
library:read | Look up libraries, items, search, random picks, recently added, genres. |
playback:events | Hear when something is played or finished. |
ui:home | Contribute a row to the home page. |
ui:detail | Contribute a panel to movie and show pages. |
http:routes | Expose endpoints under /api/plugins/<id>/. |
schedule | Run a function on an interval, never faster than once a minute. |
notify | Send a message through the server's notification channel. |
network | Reach the internet. |
storage | Key and value storage. Always granted, listed so the owner sees it. |
A server plugin is JavaScript running inside the media server process, the same as any dependency the server installs. The permission list shapes the API a plugin receives and tells the owner what to expect. It is not a sandbox, and it does not stop determined code from doing anything the runtime can do.
That is the same arrangement as Plex channels, Jellyfin plugins and browser extensions. Install plugins you trust, and read the source when you can. Review before listing here is a check on obvious problems, and it is not a security guarantee.
The server half
module.exports = {
async setup(plugin) {
// library:read
plugin.library.libraries();
plugin.library.item(1234);
plugin.library.search('blade runner', 20);
plugin.library.format(item);
// ui:home, return null and nothing renders
plugin.homeRow('key', (ctx) => {
return { title: 'A row title', items: [ /* items */ ] };
});
// http:routes, reachable at /api/plugins/<your-id>/thing
plugin.route('get', 'thing', (req, res) => res.json({ ok: true }));
// playback:events
plugin.on('playback:finished', (info) => {});
// schedule
plugin.every('30m', async () => {});
// storage, survives restarts
plugin.storage.set('cursor', 42);
plugin.storage.get('cursor', 0);
},
async teardown() {}
};
Home rows have a four second budget. A row that takes longer is skipped for that page load, so a slow plugin cannot stall the home page.
The browser half
plugin.onDetail((item, mount) => {
const el = document.createElement('div');
el.className = 'panel-card';
el.textContent = item.title + ' came out in ' + item.year;
mount.appendChild(el);
});
const data = await (await plugin.api('thing')).json();
plugin.ui.card(item);
plugin.ui.play(itemId);
plugin.toast('done');
Client code is wrapped in a try and catch by the host, so a mistake logs an error rather than breaking the page.
Installing and publishing
While writing one, drop the folder into the server's data\plugins\ directory. It appears in Settings, Plugins, turned off. Turning it on starts it, and saving its settings reloads it, which is the fastest way to pick up code changes without restarting the server.
To share it, zip the folder so that plugin.json sits at the archive root or inside a single folder named for the plugin, publish the zip at an HTTPS address, and submit it on the plugins page.
Every submission is reviewed before it is listed, and a new version of an approved plugin returns to pending. That prevents an approved listing being swapped for different code after review.
Automation
Requests can be wired to Radarr, Sonarr and Lidarr so that asking for something starts the process of getting it.
| Request type | Service | API | Matched on |
|---|---|---|---|
| movie | Radarr | /api/v3 | tmdbId |
| show | Sonarr | /api/v3 | tvdbId |
| music | Lidarr | /api/v1 | foreignArtistId |
Set each service's address and API key under Settings, Maintenance. The first quality profile and the first root folder are used, so make sure the ones you want are first in each service.
What happens to a request
- Someone asks for a title and marks it as a movie, show or music.
- The matching service is asked to look the title up, and the first result is added as monitored with a search started immediately.
- Every five minutes the download queue is checked. The request shows searching, then downloading with a percentage.
- When the file lands, the request flips to done and a notification is posted.
An owner can also send any request to a service by hand from the requests inbox. Subtitles are handled by the built in captioning, so Bazarr is not needed.
Owner tools and maintenance
The health panel
Settings, Maintenance shows a list of checks with plain verdicts. It covers the library database, the transcoder binary, whether a hardware encoder was found, the scanner binary, the hub connection with the age of the last heartbeat, remote access, the notification webhook, the captioning key, background queue depths, and free space on every drive holding a library.
Backups
Settings, Maintenance has a one click backup that downloads accounts.db. That single file holds accounts, profiles, PINs, watch history, playlists, ratings, comments and requests.
To restore it, stop the server, put the file back at C:\ProgramData\Kinode Media Server\data\accounts.db, and start the server. Your media files are not in the backup because they are not the server's to hold.
Library audit and reclaim
Settings, Library audit runs four checks and offers a reclaim list:
- Duplicate versions, meaning one film with more than one file.
- Below 720p, sorted by height.
- Season gaps, listing shows that have episode 1 through 4 and then 6.
- Movies with no subtitles of any kind, embedded or sidecar.
- Reclaim, listing large files that were watched, rated low, or untouched for a year, ranked by space returned.
Nothing is deleted for you. The list is a starting point and the decisions are yours.
Notifications and the weekly digest
Set a Discord webhook, or the bot, under Settings, Maintenance. The server then sends:
- New episodes of shows you followed.
- Newly added films and series.
- A recap on Sunday evening with hours watched, the most watched title, per person leaders and data served.
- One warning per day for any library drive above 90 percent full.
- Movie night reminders an hour before and at the start.
Statistics
Every profile has a statistics page with hours watched, plays, titles, a thirty day chart, a time of day histogram, a playback mode breakdown, most watched, recently watched and a year to date summary.
Hours are real time watched. The server adds the gap between progress reports, capped at sixty seconds so a laptop waking from sleep cannot bank hours it did not play. Runtimes are never summed, so abandoning a film after ten minutes counts as ten minutes.
Owners additionally see server wide totals, a usage calendar, and a live monitor with processor load, memory, streaming bandwidth counted at the byte level, and free space per drive.
Hub accounts, passwords and recovery
Accounts on this website are separate from accounts on your server. There are four ways back into a hub account:
- Change your password while signed in.
- Recovery codes. Eight single use codes, shown once when you register, stored as hashes. They need no email and no administrator.
- A reset link by email, sent to the address on your account.
- An administrator generated link, for when the other three fail.
For your own server, a forgotten password is fixed on the machine itself:
cd "C:\Program Files\Kinode Media Server\app"
node tools\reset-password.js lists the accounts
node tools\reset-password.js owner sets a generated password
node tools\reset-password.js owner mypass sets one you choose
Troubleshooting
Symptoms first, then the causes worth checking in the order worth checking them.
The page will not load at all
Check the service and start it if it is stopped.
sc query KinodeMediaServer
net start KinodeMediaServerOn the machine itself use http://localhost:5466. From another device use the machine's local address, for example http://192.168.1.20:5466. Older builds used 32402, so an old bookmark will fail.
Read the log, which is the fastest way to a real answer.
type "C:\ProgramData\Kinode Media Server\logs\KinodeMediaServer.err.log"The service will not start
See the next entry. This is the most common cause.
The service runs as Local System and needs to write to C:\ProgramData\Kinode Media Server. If that folder was moved or its permissions were changed, the server cannot create its database or its signing key. Recreate the folder and allow Users to modify it.
Stop the service and use the Start Kinode Media Server.cmd shortcut. Errors that a service swallows are printed in the console.
Port already in use
netstat -ano | findstr :5466
tasklist /fi "pid eq 1234"
The first command prints the process identifier in the last column. The second names it.
Edit kinode-service.xml in the install folder, change the PORT value, then reinstall the service and add a firewall rule for the new port.
net stop KinodeMediaServer
"C:\Program Files\Kinode Media Server\kinode-service.exe" uninstall
"C:\Program Files\Kinode Media Server\kinode-service.exe" install
net start KinodeMediaServer
netsh advfirewall firewall add rule name="Kinode Media Server" ^
dir=in action=allow protocol=TCP localport=5500 profile=private,domainAn older instance started from a console window keeps the port. Close that window, or end the node.exe process holding the port.
It works on the server but not from other devices
Add the rules by hand in an administrator command prompt.
netsh advfirewall firewall add rule name="Kinode Media Server" ^
dir=in action=allow protocol=TCP localport=5466 profile=private,domain
netsh advfirewall firewall add rule name="Kinode Media Server discovery" ^
dir=in action=allow protocol=UDP localport=5467 profile=private,domainThe rules only apply to private and domain networks. Open Settings, Network, and set the connection to Private. This is common after a router change, because Windows treats the reconnected network as new.
Some routers, and most guest networks, stop devices talking to each other. If a phone on wifi cannot reach a server on ethernet, check for a setting called AP isolation or client isolation and turn it off.
Some suites replace the Windows firewall entirely, and netsh rules mean nothing to them. Add the rule in that product's own interface.
Video plays but there is no sound
Browsers cannot decode these. Chrome plays the file with silence rather than reporting an error, which is why it looks like a speaker problem. The server treats these as unsupported so audio is rebuilt automatically, and the mode tag will say Direct Stream rather than Direct Play.
If it is still silent, the file was probably forced to Direct Play. Turn Auto Convert back on in the player's settings menu.
Files with several tracks sometimes default to a commentary or a foreign language track. Open the player settings menu and pick another under Audio.
Worth ruling out. Right click the tab and check.
Everything converts when it should direct play
Choosing 1080p, 720p or 480p always converts, because resizing requires decoding. Set quality back to Original.
This is the usual answer. The video is fine and the audio forces a rebuild, which shows as Direct Stream. The cost is small and it is not worth avoiding.
Safari and recent Edge handle HEVC. Firefox generally does not. Chrome depends on the machine. When the browser cannot prove it decodes HEVC, the server converts, which is expensive on 4K.
Play 4K HEVC in a browser that supports it, or use the Android app.
The player names the actual cause, for example that the audio is DTS or the container is AVI. Settings, Activity shows the same reason for every running stream.
Buffering or stuttering
Settings, Activity shows how many streams are converting. Software conversion of 4K in real time needs a strong processor, and two at once needs a very strong one. Pick a lower quality, or store a version the client can direct play.
The health panel says whether one was found. The bundled ffmpeg uses the processor, which is slower than a graphics card would be. This is a known limitation of the current build.
A 4K remux can exceed 80 Mbps sustained, which is more than many wireless links deliver reliably. Test with a wired device to separate a network problem from a server problem.
Several streams from one mechanical drive, or a network share, can starve playback while the processor looks idle.
Seeking is slow on converted video
A converted stream is a live pipe out of ffmpeg. There is no file to jump around inside. Seeking kills the pipe, restarts ffmpeg at the target second, and resumes, so there is a pause of a second or two.
Direct play seeks instantly, because the browser is reading a real file. Another reason to prefer files that direct play.
Subtitles are missing or out of sync
PGS and VobSub are pictures and cannot be displayed as browser subtitles. They are not listed at all. Use a sidecar file or the Generate subtitles button.
A sidecar has to share the base name of the media file and sit in the same folder.
Blade Runner (1982).mkv
Blade Runner (1982).srt works
Blade Runner (1982).en.srt works
subs\Blade Runner.srt not foundSubtitle timings are absolute while a restarted conversion resets the clock to zero. The server shifts the subtitle file to compensate on each restart. If they are still wrong, pause and resume to force a refresh.
Then the file itself is mistimed, usually because it was made for a different release. Download another one from the subtitle search in the player.
No artwork, or the wrong artwork
Titles appear before posters. Give a large library some time after the first scan.
Wrong poster nearly always means wrong match, especially for remakes. Rename to Title (Year).ext and rescan.
If every item shows a generic icon, artwork is failing to load rather than failing to match. Open the browser console and look for failing image requests.
A scan finds nothing
This is a genuine gap in the current build rather than a misconfiguration. The bundled installer ships playback and everything around it, and the component that indexes folders is part of the attached mode.
Until a built in scanner ships, a bundled install needs to be pointed at an existing library database. Create data\engine.json in the data directory with paths to an existing engine:
{
"db": "C:\\path\\to\\library.db",
"scanner": "C:\\path\\to\\scanner.exe"
}
The bundled ffmpeg is still used for playback, because bundled binaries take priority. Check the forum for progress on this.
A service runs as Local System, which has no access to mapped network drives created by your user account. Use a UNC path such as \\nas\media, or run the server in a window under your own account to test.
A TV library pointed at a folder of loose films finds nothing, because it expects show and season directories.
Remote access does not work
The firewall rule is not enough. The router has to forward external 5466 to the machine. Verify from a phone on mobile data rather than wifi, because a phone on your own wifi will succeed for the wrong reason.
DHCP hands out a different address after a reboot and the forward now points at nothing. Give the machine a reservation on the router.
Heartbeats are every five minutes. Restart the server to send one immediately. The health panel shows the age of the last successful one.
Some internet providers, and most mobile broadband, do not give you a real public address. Port forwarding cannot work at all in that case. Ask your provider about a public address, or use a tunnel service.
A router behind another router means the forward has to exist on both. The symptom is a forward that looks correct and does nothing.
Cannot sign in to a server
Server accounts and hub accounts are separate. The hub account signs in at usekinode.app. The server account signs in at your server's own address.
Reset it on the machine.
cd "C:\Program Files\Kinode Media Server\app"
node tools\reset-password.js
With no arguments it lists the accounts. With a username it sets a new password and prints it.
Sign in tokens are signed with a key stored at data\secret.key. If that file is deleted or the data directory moved, every existing token becomes invalid and everyone signs in again. Nothing else is lost.
A managed user sees the wrong libraries
Editing a managed user offers everything on the server, or a specific list. If the first is ticked, the list underneath is ignored.
A profile can never see more than the account it belongs to. If your access was shared to you as a subset, your managed users are bounded by that subset regardless of their own list.
Switch profile and back, or reload. Clients remember the profile they last used.
The Discord bot is offline
Resetting a bot token in the developer portal invalidates the old one immediately. The error is explicit about an invalid token.
The bug intake channel needs it. Without it, the bot connects and sees empty message bodies. Enable it under Bot, Privileged Gateway Intents.
With a guild identifier set they register immediately. Registered globally they take up to an hour. Set DISCORD_GUILD_ID.
Use the Test button in Settings, Maintenance. It calls the bot's ping endpoint and reports what answered.
The bot must be able to join and speak in the voice channel, and you have to be in one when you ask.
Email is not arriving
Two real failures that are worth knowing about, because both were hit in practice and neither is obvious.
This happens when the sending address belongs to a domain that has not authorised the sending service. A message claiming to be from a Gmail address, signed by Amazon, is exactly the shape of a phishing attempt and filters treat it that way.
The fix is to send from a domain you control and have verified with the mail provider, so the address in the From line and the signature agree.
A systemd unit splits Environment= values on whitespace unless they are quoted. Configuring a sender as Kinode Media Server <noreply@example.com> without quotes delivers the single word Kinode to the program, the mail provider rejects it as an invalid address, and a catch block swallows the error so nothing appears anywhere.
Environment=MAIL_FROM=Kinode Media Server <noreply@example.com>
Environment="MAIL_FROM=Kinode Media Server <noreply@example.com>"
The first line is wrong and the second is right. The lesson generalises: swallowing an error is right for the request and wrong for the operator, so every send is now logged with its identifier or its real error.
A mail provider in sandbox or trial mode delivers only to addresses that have been verified in advance. Everyone else silently receives nothing. Check whether production access has been granted.
A plugin will not load
The folder has to be named exactly as id in the manifest. This is checked and refused.
A trailing comma is the usual culprit. The plugin list shows the reason next to the plugin.
The plugin is disabled and the message is shown. Read the server log for the stack.
Check the permissions in its manifest. A plugin that did not ask for ui:home has its home rows quietly ignored, and the log says so.
Saving its settings reloads it. That is quicker than restarting the server.
A drive is full
Settings, Library audit lists large files that were watched, rated low, or untouched for a year, ranked by space returned.
Seek preview images live in data\trickplay\ and downloaded subtitles in data\subtitles\. Both are safe to delete, and both rebuild when needed.
Converted streams write segments to the system temporary directory. Sessions nobody touched for five minutes are cleaned up automatically, and a crash can leave some behind. Clearing %TEMP% while the server is stopped is safe.
Any library drive above 90 percent full triggers one notification per day, if a webhook or bot is configured.
Glossary
- Attached mode
- Running against an existing library database and external binaries, configured through
engine.jsonorENGINE_*variables. - Bundled install
- What the installer produces. The runtime and a full ffmpeg ship inside, and nothing else needs to be present.
- Container
- The file format wrapping the audio and video, for example MKV or MP4. Separate from the codecs inside it.
- Direct play
- The original file is sent untouched. Cheapest for the server and best for seeking.
- Direct stream
- Video is copied as-is while audio or the container is rebuilt. Costs very little.
- Converting
- Video is decoded and re-encoded. The expensive one.
- Guest link
- A link to one title that works without an account and expires on its own.
- Hub
- This website. An account system and a directory for finding servers. It never handles video.
- Managed user
- A person on one server with a display name, an optional PIN, and their own watch history. No password, no email, and unknown to the hub.
- Owner
- The person who runs a server. The word admin is reserved for staff on this website.
- Part
- One physical file belonging to an item. A film with a 4K and a 1080p copy has two parts.
- Profile
- Another word for managed user, and the account owner counts as one.
- Remux
- Repackaging streams into a different container without re-encoding. What direct stream does.
- Sidecar
- A file beside the media file sharing its base name, most often a subtitle.
- Trickplay
- The preview images shown when hovering the seek bar.
- Watch together
- Synchronised playback in a room joined with a short code.
Questions people ask
Does any of this cost money?
No. There is no paid tier and no feature is held back. Remote streaming, hardware transcoding, sharing and everything else are included.
Do my files get uploaded anywhere?
No. Your media never leaves your machine except when you or someone you shared with streams it, and that connection is direct. The only thing sent to this website is a heartbeat with your server's name and addresses.
Can you see what I watch?
No. Watch history lives in your server's database. The hub has no table for it and never receives it.
Do I need Plex installed?
For playback, no, because a complete ffmpeg is bundled. For indexing a library today, a bundled install still needs to be attached to an existing library database. That gap is described in the scan entry.
Will it run on a Raspberry Pi or a NAS?
Not from these installers, which are Windows only. The server is JavaScript and there is nothing Windows specific about it, so running from source elsewhere is plausible and untested.
How many people can watch at once?
As many as direct play as your network allows, because the server is only reading files. Converting is what limits you, and one 4K conversion can occupy a strong processor entirely.
Why is my processor busy when nobody is watching?
Background jobs. Seek previews, loudness measurement and subtitle generation all run one at a time when the server is idle. They finish and stop.
Can I move the server to a new machine?
Yes. Install it on the new machine, stop both servers, copy C:\ProgramData\Kinode Media Server\data\accounts.db across, and start it. Accounts, profiles and watch history come with it. Library paths have to point somewhere valid on the new machine.
Is my connection encrypted?
Inside your house it is plain HTTP, which is normal for a local service. Remote access is also plain HTTP unless you put your own domain and certificate in front of it and set the public address. Anything sensitive deserves that certificate.
What happens if this website disappears?
Your server keeps working. The hub is a directory. Local access, playback, accounts and profiles are all local, and only finding your server from outside your house would need setting up another way.