<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged jellyfin</title>
<link>https://coderlegion.com/tag/jellyfin</link>
<description>Powered by Question2Answer</description>
<item>
<title>Smart Home Cinema for Jellyfin</title>
<link>https://coderlegion.com/24716/smart-home-cinema-for-jellyfin</link>
<description>&lt;p&gt;Smart Home Cinema for Jellyfin is a Windows voice-control layer for an existing Jellyfin setup.&lt;/p&gt;
&lt;p&gt;It works with your own Jellyfin Server and compatible Jellyfin TV/client devices, adding hands-free control for movie playback without replacing Jellyfin Server or the Jellyfin apps you already use.&lt;/p&gt;
&lt;p&gt;The original Smart Home Cinema launch on Coder Legion focused on local VLC and PotPlayer playback. This launch covers the separate Jellyfin package, which includes three control editions.&lt;/p&gt;
&lt;h3 id=&quot;three-editions-in-one-jellyfin-package&quot;&gt;Three editions in one Jellyfin package&lt;/h3&gt;
&lt;h4 id=&quot;local-voice-edition&quot;&gt;Local Voice Edition&lt;/h4&gt;
&lt;p&gt;Use a microphone connected to the Windows PC and speak supported commands directly.&lt;/p&gt;
&lt;p&gt;Voice recognition runs locally on the PC, so normal voice-command recognition does not require Alexa, Google Assistant, TRIGGERcmd, or a smart speaker.&lt;/p&gt;
&lt;h4 id=&quot;voice-assistant-edition&quot;&gt;Voice Assistant Edition&lt;/h4&gt;
&lt;p&gt;Use Alexa or Google Assistant as the voice input path, with TRIGGERcmd bridging the assistant commands to Smart Home Cinema on the Windows PC.&lt;/p&gt;
&lt;p&gt;This edition is designed for users who already prefer voice-assistant routines around the home.&lt;/p&gt;
&lt;h4 id=&quot;multi-zone-edition&quot;&gt;Multi-Zone Edition&lt;/h4&gt;
&lt;p&gt;Multi-Zone uses the same Alexa / Google Assistant and TRIGGERcmd input path, but changes how destinations are controlled.&lt;/p&gt;
&lt;p&gt;It supports up to four independently configured Jellyfin viewing zones. Each zone can have its own Jellyfin user, compatible TV/client device, and Smart Home Cinema Movie List playlist.&lt;/p&gt;
&lt;p&gt;The destination is included directly in the command, for example:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TV One Pause Movie&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;TV Two Play Movie Five&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Multi-Zone is designed for independent room control. It does not synchronize playback across several televisions.&lt;/p&gt;
&lt;h3 id=&quot;one-selected-tv-or-independent-zones&quot;&gt;One selected TV or independent zones&lt;/h3&gt;
&lt;p&gt;The standard Jellyfin editions — Local Voice and Voice Assistant — can configure up to four TV/client targets, with one selected target controlled at a time.&lt;/p&gt;
&lt;p&gt;That works well when one person or one household profile moves between different televisions.&lt;/p&gt;
&lt;p&gt;Multi-Zone is intended for homes where rooms or users should remain independent. Each command names the zone directly, so one room can be controlled without changing the target used by another.&lt;/p&gt;
&lt;h3 id=&quot;what-you-can-control&quot;&gt;What you can control&lt;/h3&gt;
&lt;p&gt;The Jellyfin package supports a practical set of movie-control commands, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pause, resume, restart and stop playback&lt;/li&gt;
&lt;li&gt;seek forward or rewind in several intervals&lt;/li&gt;
&lt;li&gt;next and previous playlist items&lt;/li&gt;
&lt;li&gt;playback progress&lt;/li&gt;
&lt;li&gt;a numbered Movie List&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Play Movie One&lt;/code&gt; through &lt;code&gt;Play Movie Twenty&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;supported Jellyfin views and navigation commands&lt;/li&gt;
&lt;li&gt;switching the selected TV in the standard editions&lt;/li&gt;
&lt;li&gt;direct zone commands in Multi-Zone&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The numbered Movie List gives each supported movie command a clear position that can also be shown on the television.&lt;/p&gt;
&lt;p&gt;Client capabilities differ across Jellyfin platforms, so Smart Home Cinema exposes only the behavior supported by the target client rather than assuming every TV app behaves the same way.&lt;/p&gt;
&lt;h3 id=&quot;built-for-an-existing-jellyfin-setup&quot;&gt;Built for an existing Jellyfin setup&lt;/h3&gt;
&lt;p&gt;Smart Home Cinema does not provide movies, host a media library, or replace Jellyfin.&lt;/p&gt;
&lt;p&gt;Your media remains in your own Jellyfin environment. Jellyfin Server continues to manage the library and playback, while Smart Home Cinema runs on the Windows PC and adds the voice-control workflow around it.&lt;/p&gt;
&lt;p&gt;The Jellyfin package requires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows 10 or Windows 11, 64-bit&lt;/li&gt;
&lt;li&gt;an existing Jellyfin Server&lt;/li&gt;
&lt;li&gt;a compatible Jellyfin TV/client device&lt;/li&gt;
&lt;li&gt;the Jellyfin app running on that device&lt;/li&gt;
&lt;li&gt;a microphone for Local Voice, or Alexa / Google Assistant with TRIGGERcmd for the assistant-based editions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TV/client support and available commands vary by platform, so there is a dedicated compatibility table on the Smart Home Cinema website.&lt;/p&gt;
&lt;h3 id=&quot;trial-and-licensing&quot;&gt;Trial and licensing&lt;/h3&gt;
&lt;p&gt;The Jellyfin package includes all three editions in the same download.&lt;/p&gt;
&lt;p&gt;A fully functional 14-day trial is available with no credit card required. Smart Home Cinema uses a one-time license rather than a subscription.&lt;/p&gt;
&lt;p&gt;One Smart Home Cinema license applies to the Smart Home Cinema product family on the licensed Windows PC, covering both the VLC / PotPlayer and Jellyfin packages.&lt;/p&gt;
&lt;p&gt;Smart Home Cinema is an independent third-party product compatible with Jellyfin. It is not affiliated with, endorsed by, sponsored by, or maintained by the Jellyfin project.&lt;/p&gt;
&lt;p&gt;For the engineering story behind turning the working Jellyfin control layer into a distributable Windows product, I also wrote:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/24715/a-working-connector-is-not-a-product-what-it-took-to-ship-jellyfin-voice-control-on-windows&quot;&gt;A Working Connector Is Not a Product: What It Took to Ship Jellyfin Voice Control on Windows&lt;/a&gt;&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/24716/smart-home-cinema-for-jellyfin</guid>
<pubDate>Sun, 16 Aug 2026 13:26:40 +0000</pubDate>
</item>
<item>
<title>A Working Connector Is Not a Product: What It Took to Ship Jellyfin Voice Control on Windows</title>
<link>https://coderlegion.com/24715/a-working-connector-is-not-a-product-what-it-took-to-ship-jellyfin-voice-control-on-windows</link>
<description>&lt;p&gt;&lt;em&gt;How moving from local VLC/PotPlayer control to Jellyfin required a second productization pass across configuration, dependencies, clean installs, signing, and antivirus trust.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When I first turned &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/16662/what-smart-home-cinema-taught-me-about-building-reliable-local-automation-on-windows&quot;&gt;&lt;strong&gt;Smart Home Cinema – Voice Control&lt;/strong&gt;&lt;/a&gt; from a personal Windows automation project into software other people could use, the project was centered on local movie playback with VLC and PotPlayer.&lt;/p&gt;
&lt;p&gt;That earlier work taught me that reliable automation and distributable software are different engineering problems. In my first Coder Legion article, I focused mainly on the local-control side of that story: making voice-driven playback predictable, dealing with player behavior, reducing hidden assumptions, and moving from personal automation toward something that could behave like a product.&lt;/p&gt;
&lt;p&gt;The productization side appeared there too, but mostly as one part of a broader reliability story.&lt;/p&gt;
&lt;p&gt;Jellyfin gave me a reason to return to that subject in much more detail.&lt;/p&gt;
&lt;p&gt;By the time Smart Home Cinema – Voice Control expanded to Jellyfin, the project already had an established Windows productization model. What changed was the environment: Jellyfin introduced a new set of configuration, runtime, and deployment assumptions that the earlier VLC/PotPlayer branch did not have to handle.&lt;/p&gt;
&lt;p&gt;Local VLC/PotPlayer control kept the player, the automation layer, and the media workflow on the Windows machine. Jellyfin introduced a server/client architecture. Playback could happen on a television while the Windows control software ran elsewhere. The product now had to deal with server configuration, discovered clients, target selection, additional runtime dependencies, and a release path that had to work across that larger system.&lt;/p&gt;
&lt;p&gt;At the stage covered here, the two Jellyfin editions being prepared for distribution were &lt;strong&gt;Voice Assistant&lt;/strong&gt; and &lt;strong&gt;Local Voice&lt;/strong&gt;. Voice Assistant accepted commands through an external assistant path. Local Voice used a microphone connected to the PC and local speech recognition. Both controlled Jellyfin, but they entered the system through different input paths and required different runtime components.&lt;/p&gt;
&lt;p&gt;The core Jellyfin control path already worked. A command could reach the Windows control layer, pass through the Jellyfin integration, and produce the expected action on the selected television.&lt;/p&gt;
&lt;p&gt;So the question was no longer whether working software needed to be productized.&lt;/p&gt;
&lt;p&gt;It was this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What does product-ready mean when the architecture itself has changed?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Some lessons from the classic branch carried over. Others appeared only because Jellyfin introduced assumptions that had never existed in the local-player version.&lt;/p&gt;
&lt;h3 id=&quot;a-working-exe-is-not-a-release&quot;&gt;A Working EXE Is Not a Release&lt;/h3&gt;
&lt;p&gt;One of the first Jellyfin-specific problems was surprisingly basic: knowing which build was actually the release build.&lt;/p&gt;
&lt;p&gt;During development, several workspaces and intermediate outputs had accumulated. That is normal while experimenting, testing fixes, and separating editions. It also creates a release risk.&lt;/p&gt;
&lt;p&gt;An executable can launch successfully and still be the wrong executable to ship.&lt;/p&gt;
&lt;p&gt;The important questions became:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which source tree is authoritative?&lt;/li&gt;
&lt;li&gt;Which outputs were built from that source?&lt;/li&gt;
&lt;li&gt;Which files belong in the final payload?&lt;/li&gt;
&lt;li&gt;Which files are development artifacts?&lt;/li&gt;
&lt;li&gt;Which components are intentionally different between editions?&lt;/li&gt;
&lt;li&gt;Can the release be reconstructed from a controlled staging area rather than from whatever happens to be on the workstation?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The solution was to treat &lt;strong&gt;release provenance&lt;/strong&gt; as part of the Jellyfin release process.&lt;/p&gt;
&lt;p&gt;Voice Assistant and Local Voice received separate authoritative release-candidate staging areas. The final installers were built from those controlled payloads rather than from arbitrary development directories.&lt;/p&gt;
&lt;p&gt;This sounds procedural, but it changed the meaning of &quot;the build works.&quot;&lt;/p&gt;
&lt;p&gt;A release candidate now had an identity. I could say where it came from, what belonged in it, and why.&lt;/p&gt;
&lt;p&gt;That mattered because the two Jellyfin editions shared some components while legitimately differing in others. The Setup Tool and activation component could be identical across both editions. The control components were allowed to diverge where Local Voice required additional behavior.&lt;/p&gt;
&lt;p&gt;Shared architecture did not require every binary to have the same hash.&lt;/p&gt;
&lt;h3 id=&quot;familiar-problem-new-configuration&quot;&gt;Familiar Problem, New Configuration&lt;/h3&gt;
&lt;p&gt;Configuration itself was not new to me. The classic editions had already taught me that a product cannot rely on the developer manually setting up the environment.&lt;/p&gt;
&lt;p&gt;Jellyfin changed what had to be configured.&lt;/p&gt;
&lt;p&gt;On my development machine, I already knew:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the Jellyfin server I was testing against;&lt;/li&gt;
&lt;li&gt;how the application should connect to it;&lt;/li&gt;
&lt;li&gt;which detected client belonged to which physical television;&lt;/li&gt;
&lt;li&gt;which target was currently selected;&lt;/li&gt;
&lt;li&gt;where supporting tools were installed;&lt;/li&gt;
&lt;li&gt;which folders contained runtime assets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That knowledge was specific to the new architecture. A distributable Jellyfin edition needed a way to establish it on another machine.&lt;/p&gt;
&lt;p&gt;The Jellyfin Setup Tool became the user-facing boundary for that job.&lt;/p&gt;
&lt;p&gt;The user could provide the Jellyfin connection details, scan the available client sessions, identify a television, save configured targets, choose the active target, and reload the configuration later.&lt;/p&gt;
&lt;p&gt;The Identify step was especially useful. A list of discovered clients is meaningful to a developer who has spent hours watching session data. It is much less useful to someone standing in a living room trying to answer a simple question:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;Which one of these entries is my TV?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The product had to bridge that gap.&lt;/p&gt;
&lt;p&gt;That led to a rule I still find useful:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Any knowledge required for normal operation that exists only in the developer's head is unfinished product work.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;runtime-state-needs-a-stable-home&quot;&gt;Runtime State Needs a Stable Home&lt;/h3&gt;
&lt;p&gt;The Jellyfin branch introduced state that had to remain discoverable after installation and restart: server configuration, target information, shared runtime data, and other machine-level settings used by the installed software.&lt;/p&gt;
&lt;p&gt;Installed binaries and runtime state serve different purposes. The binaries belong in the installation area. Configuration and machine-level application data need a stable location that does not depend on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the source tree;&lt;/li&gt;
&lt;li&gt;the current working directory;&lt;/li&gt;
&lt;li&gt;a build folder;&lt;/li&gt;
&lt;li&gt;the account that compiled the program;&lt;/li&gt;
&lt;li&gt;a folder that happened to exist during testing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The Jellyfin editions therefore kept persistent runtime information in shared application data outside the installation directory.&lt;/p&gt;
&lt;p&gt;The exact file layout is not the interesting part. The important point is that the installed program could discover its own state without inheriting paths from my workstation.&lt;/p&gt;
&lt;p&gt;This also created a cleaner boundary between shared responsibilities and edition-specific behavior. Voice Assistant and Local Voice could use common machine-level state where the responsibility was genuinely shared, while keeping their own runtime components where their behavior differed.&lt;/p&gt;
&lt;h3 id=&quot;ffmpeg-was-easy-until-it-had-to-ship&quot;&gt;FFmpeg Was Easy Until It Had to Ship&lt;/h3&gt;
&lt;p&gt;During Jellyfin development, FFmpeg was simple.&lt;/p&gt;
&lt;p&gt;It existed.&lt;/p&gt;
&lt;p&gt;The Jellyfin Connector could use it when it needed to generate video assets for parts of the TV-facing experience. As long as the tool was present in the expected environment, there was little reason to think about it.&lt;/p&gt;
&lt;p&gt;Distribution changed the problem completely.&lt;/p&gt;
&lt;p&gt;A user's machine might not have FFmpeg at all. If it did, the version or location could be different. Relying on the system PATH would make the product depend on unrelated software choices made by the user.&lt;/p&gt;
&lt;p&gt;The dependency needed a predictable lifecycle.&lt;/p&gt;
&lt;p&gt;An early approach treated the FFmpeg binaries as files that could simply be bundled with the payload. That approach was replaced with a controlled dependency-bootstrap flow.&lt;/p&gt;
&lt;p&gt;The installer could obtain the required package, verify its integrity, extract the required tools, place them in a deterministic shared runtime location, and clean up the temporary files afterward.&lt;/p&gt;
&lt;p&gt;That small sentence hides several failure modes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;download fails
checksum does not match
archive extraction fails
required executable is missing
copy fails
cleanup fails
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those problems had nothing to do with whether the Connector itself could generate an asset correctly. They appeared because a tool that was simply present during development now had to become a dependable part of somebody else's installation.&lt;/p&gt;
&lt;p&gt;FFmpeg stopped being merely &quot;a tool the Connector uses.&quot; Its delivery and failure behavior became part of the release system.&lt;/p&gt;
&lt;h3 id=&quot;failure-paths-are-part-of-the-product&quot;&gt;Failure Paths Are Part of the Product&lt;/h3&gt;
&lt;p&gt;The dependency bootstrap also produced a useful installer bug.&lt;/p&gt;
&lt;p&gt;The happy path worked.&lt;/p&gt;
&lt;p&gt;The failure path had a problem.&lt;/p&gt;
&lt;p&gt;In one error-handling branch, the order between cleanup and error reporting meant that cleanup could be skipped when the operation failed. The fix was straightforward once found: cleanup had to happen before the operation that propagated the error.&lt;/p&gt;
&lt;p&gt;The more important change was in testing.&lt;/p&gt;
&lt;p&gt;I started treating negative cases as release tests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;bad checksum
    → dependency is not installed
    → temporary files are cleaned

failed download
    → dependency is not installed
    → temporary files are cleaned
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A partial installation must not look like a successful one.&lt;/p&gt;
&lt;p&gt;Once the installer itself can download, validate, extract, and place runtime components, its own failure behavior becomes part of product reliability.&lt;/p&gt;
&lt;p&gt;The useful question is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What state does the machine end up in when the operation fails halfway through?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a distributable dependency, that answer matters as much as the success path.&lt;/p&gt;
&lt;h3 id=&quot;correct-work-can-still-look-broken&quot;&gt;Correct Work Can Still Look Broken&lt;/h3&gt;
&lt;p&gt;Another FFmpeg issue was not a failure at all.&lt;/p&gt;
&lt;p&gt;Preparing the dependency could take several minutes.&lt;/p&gt;
&lt;p&gt;From the installer's point of view, work was happening normally. From the user's point of view, the window could appear frozen.&lt;/p&gt;
&lt;p&gt;That is enough to create a support problem.&lt;/p&gt;
&lt;p&gt;A user does not see internal progress unless the product makes it visible. Waiting without feedback is easy to interpret as a crash.&lt;/p&gt;
&lt;p&gt;The installer was updated with explicit messages explaining that the dependency download and preparation could take time. The setup page also changed its status text while the work was running.&lt;/p&gt;
&lt;p&gt;Nothing about the underlying FFmpeg process became faster.&lt;/p&gt;
&lt;p&gt;The user simply gained enough information to understand the state of the installation.&lt;/p&gt;
&lt;p&gt;That was a small change compared with the architecture, but it belonged in the same release process.&lt;/p&gt;
&lt;h3 id=&quot;documentation-became-part-of-the-payload&quot;&gt;Documentation Became Part of the Payload&lt;/h3&gt;
&lt;p&gt;The final staging areas included the appropriate README, license information, third-party notices, and edition-specific documentation.&lt;/p&gt;
&lt;p&gt;A file relevant to the Voice Assistant edition was deliberately excluded from Local Voice rather than copied into both packages for convenience.&lt;/p&gt;
&lt;p&gt;That distinction matters when one product family contains editions with overlapping components but different runtime paths.&lt;/p&gt;
&lt;p&gt;A clean release is not the folder with the largest number of files. It is the smallest complete payload that accurately represents the edition being installed.&lt;/p&gt;
&lt;p&gt;Documentation and third-party material were therefore validated alongside the executables rather than added casually at the end.&lt;/p&gt;
&lt;h3 id=&quot;clean-install-now-had-to-reach-the-television&quot;&gt;Clean Install Now Had to Reach the Television&lt;/h3&gt;
&lt;p&gt;Clean-install testing was another familiar discipline whose meaning changed with Jellyfin.&lt;/p&gt;
&lt;p&gt;For a local Windows playback product, the important runtime stays close to the machine being tested.&lt;/p&gt;
&lt;p&gt;The Jellyfin editions extended the path beyond it.&lt;/p&gt;
&lt;p&gt;For the Jellyfin editions of &lt;strong&gt;Smart Home Cinema – Voice Control&lt;/strong&gt;, the clean-install pass had to exercise the complete operational chain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;installation and the expected wizard flow;&lt;/li&gt;
&lt;li&gt;initialization of required product state;&lt;/li&gt;
&lt;li&gt;configuration through the Setup Tool;&lt;/li&gt;
&lt;li&gt;Jellyfin server connection;&lt;/li&gt;
&lt;li&gt;TV discovery;&lt;/li&gt;
&lt;li&gt;Identify;&lt;/li&gt;
&lt;li&gt;saving a configured TV;&lt;/li&gt;
&lt;li&gt;active-target persistence;&lt;/li&gt;
&lt;li&gt;reloading the configuration;&lt;/li&gt;
&lt;li&gt;launching the Connector;&lt;/li&gt;
&lt;li&gt;executing representative Jellyfin commands.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Local Voice added another layer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local Voice Engine startup;&lt;/li&gt;
&lt;li&gt;microphone path;&lt;/li&gt;
&lt;li&gt;wake state;&lt;/li&gt;
&lt;li&gt;sleep state;&lt;/li&gt;
&lt;li&gt;status behavior;&lt;/li&gt;
&lt;li&gt;representative commands through the local speech path.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The definition of success had expanded.&lt;/p&gt;
&lt;p&gt;A clean installation passed only when the installed product could make the full journey from configuration to visible behavior on the real Jellyfin client.&lt;/p&gt;
&lt;p&gt;A completed installer was merely the beginning of that test.&lt;/p&gt;
&lt;p&gt;This was one of the clearest differences between the two productization passes. The classic branch had taught me to validate the software outside my development environment. Jellyfin required that validation to cross a server/client boundary and end on a television.&lt;/p&gt;
&lt;h3 id=&quot;signing-was-not-the-finish-line&quot;&gt;Signing Was Not the Finish Line&lt;/h3&gt;
&lt;p&gt;The final Windows binaries and installers were code-signed.&lt;/p&gt;
&lt;p&gt;That created another release rule:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Test the artifact that will actually be distributed.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The signed build was retested after signing.&lt;/p&gt;
&lt;p&gt;It is easy to imagine the release sequence as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;build
test
sign
upload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In practice, the signed artifact is the one the user receives, so it deserves its own final verification.&lt;/p&gt;
&lt;p&gt;The signed Voice Assistant and Local Voice installers passed their clean-install regression.&lt;/p&gt;
&lt;p&gt;At that point, the Jellyfin branch had installable editions that were packaged, configurable through a user-facing tool, dependency-aware, documented, clean-install tested, and signed.&lt;/p&gt;
&lt;p&gt;The release still had one more problem.&lt;/p&gt;
&lt;h3 id=&quot;then-antivirus-called-it-a-reverse-shell&quot;&gt;Then Antivirus Called It a Reverse Shell&lt;/h3&gt;
&lt;p&gt;The outer installers looked clean in VirusTotal.&lt;/p&gt;
&lt;p&gt;One internal component did not.&lt;/p&gt;
&lt;p&gt;The shared Jellyfin Setup Tool accumulated multiple heuristic detections, including labels associated with generic malware and reverse-shell behavior.&lt;/p&gt;
&lt;p&gt;That was serious enough to stop the release.&lt;/p&gt;
&lt;p&gt;The Setup Tool was a configuration application. Its real job was to communicate with the Jellyfin server configured by the user, discover client sessions, send an Identify action, and save the selected TVs and target state.&lt;/p&gt;
&lt;p&gt;I did not want to dismiss the detections simply because I knew what the program was supposed to do.&lt;/p&gt;
&lt;p&gt;The component was audited.&lt;/p&gt;
&lt;p&gt;The review did not find the behavior suggested by those labels: no reverse-shell implementation, no arbitrary shell launcher, no persistence mechanism, and no dynamic payload behavior of the kind the detections implied.&lt;/p&gt;
&lt;p&gt;The sample was also submitted to an antivirus vendor for analysis. Bitdefender later confirmed that the file was clean and corrected its classification.&lt;/p&gt;
&lt;p&gt;That created another release decision.&lt;/p&gt;
&lt;p&gt;I did not want to rewrite a working component purely to obtain a different hash. I also did not want to add unnecessary obfuscation just to make static analysis harder.&lt;/p&gt;
&lt;p&gt;Either choice could have made the executable less explainable without improving its actual security.&lt;/p&gt;
&lt;p&gt;The release remained blocked while the classification issue was being remediated.&lt;/p&gt;
&lt;p&gt;That was an odd stage of the project. The Jellyfin editions were functional, packaged, signed, and validated through clean installation, yet they were still not ready for public distribution.&lt;/p&gt;
&lt;h3 id=&quot;productization-has-to-follow-the-architecture&quot;&gt;Productization Has to Follow the Architecture&lt;/h3&gt;
&lt;p&gt;The classic VLC/PotPlayer branch had already taught me that personal automation and product software are different disciplines.&lt;/p&gt;
&lt;p&gt;Jellyfin did not overturn that lesson. It exposed another layer of it.&lt;/p&gt;
&lt;p&gt;Some productization work carried over directly: explicit configuration, documentation, stable runtime state, clean-install testing, signed release artifacts.&lt;/p&gt;
&lt;p&gt;Other work existed because the architecture had changed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;authoritative Jellyfin release staging;&lt;/li&gt;
&lt;li&gt;server and target configuration;&lt;/li&gt;
&lt;li&gt;client discovery and identification;&lt;/li&gt;
&lt;li&gt;a runtime path that ended on a television;&lt;/li&gt;
&lt;li&gt;FFmpeg dependency bootstrap and integrity checking;&lt;/li&gt;
&lt;li&gt;dependency failure cleanup;&lt;/li&gt;
&lt;li&gt;post-signing end-to-end regression;&lt;/li&gt;
&lt;li&gt;antivirus investigation that became a release gate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For &lt;strong&gt;Smart Home Cinema – Voice Control&lt;/strong&gt;, this second productization pass was not a repeat of the first one. The underlying discipline was familiar, but the new architecture created a new set of assumptions that had to be made explicit.&lt;/p&gt;
&lt;p&gt;The earlier branch taught me to remove assumptions that belonged only to my own PC.&lt;/p&gt;
&lt;p&gt;Jellyfin showed me how quickly new ones appear when the product enters a different architecture.&lt;/p&gt;
&lt;p&gt;The Connector already worked before this stage began. The engineering work that followed was about finding everything around it that still depended on my development environment and turning those dependencies into explicit product behavior.&lt;/p&gt;
&lt;p&gt;The first branch taught me that the development machine cannot be part of the product specification.&lt;/p&gt;
&lt;p&gt;Jellyfin taught me the follow-up lesson: when the architecture changes, productization has to chase the hidden assumptions all over again.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24715/a-working-connector-is-not-a-product-what-it-took-to-ship-jellyfin-voice-control-on-windows</guid>
<pubDate>Sun, 16 Aug 2026 12:01:21 +0000</pubDate>
</item>
</channel>
</rss>