Jellyfin Media Player 1.12 ships a web engine older than Chrome 86, so
dotsContainer.replaceChildren() threw and the bar never loaded. Clear
with textContent and append instead.
Trailers whose uploader set default captions show them at the bottom of
the player. The trailer fills the bar behind the home rows, so the
captions are drawn over them.
Add a trailerCaptions option (default off, "Trailer captions" switch in
the settings panel). While it is off, the caption module is unloaded on
ready and again when playback starts, since YouTube loads it on play.
- Introduced multi-choice options for selecting libraries and trailer libraries in the settings panel.
- Implemented functionality to parse and apply filters from a list, allowing dynamic item fetching based on specified criteria.
- Updated relevant API calls to accommodate new filtering capabilities.
The descriptions leaned on git and packaging vocabulary ("unpinned refs",
"tag or commit", "unversioned") that most people configuring a media
server don't have. Same behaviour, said in terms of consequences: what
you get, what the risk is, and how to get back ("turn this off").
"Allow unpinned refs" becomes "Get updates early".
Follow-up to #175, which made "main" and "latest" resolve to the embedded
assets. Requested in review: an option, default disabled, for users who want
those refs to load live from jsDelivr as before.
When AllowUnpinnedRefs is enabled, "main" and "latest" are treated as git
refs ("latest" mapping to the main branch, as it always has); disabled, they
keep meaning embedded. Explicit values are unaffected either way: "embedded"
is always embedded, and a tag or commit always fetches from jsDelivr. The
config page gains a checkbox spelling out what enabling it means: unreleased
code, arriving within about 12 hours of a push, unversioned.
An "embedded" default only helps fresh installs: every existing server has
"main" or "latest" saved and would keep fetching a moving branch ref
forever. Those values never named a version, they meant "newest", and the
newest that matches the installed plugin is now the embedded copy. A tag
or commit still resolves to jsDelivr.
This makes the 10.11 fallback unreachable ("main" resolves to embedded
before the guard), and it was already dead: it compares
JellyfinVersionAttribute.GetVersion() to "10.11" by equality while the
assembly declares "10.11.2". Remove it, along with the config page's
client-side copy of the latest-to-main rewrite.
The injected index.html loaded both files from cdn.jsdelivr.net at
{{Config.VersionString}}, default "main". A branch ref is not a version:
jsDelivr re-resolves it at request time, so browser-side changes reach
every install the day they land, with no release and no way to decline,
and the code in the browser drifts from the installed plugin version. It
also leaves the media bar broken on servers without internet access.
Embed the files in the assembly and serve them from the existing
/MediaBar/{file} route:
- slideshowpure.js/css become EmbeddedResource under the Inject namespace
- Inject/index.html templates an asset base URL instead of a bare ref
- VersionString defaults to "embedded", which resolves to ../MediaBar
(relative, so it works when Jellyfin is hosted under a base path)
Any other value is still treated as a git ref and fetched from jsDelivr.
IndexHtml() normalised VersionString in place ("latest" to "main", and
"main" to "10.11" on 10.11 builds). Those assignments mutate the live
configuration, so every home page load overwrote the value the
administrator saved, and concurrent requests raced over the field.
Compute the resolved version into a local instead.
The manifest resource stream was null-forgiven, so a request for any file
outside the embedded Inject.* set threw mid-action and surfaced as a 400
with a stack trace in the server log. Return NotFound instead, and dispose
the reader.
The background behind the home page is Jellyfin's own backdrop layer, and it
has no connection to what the bar is showing. It sits on unrelated library art,
and where the user has Jellyfin's backdrop rotator enabled it also cycles on its
own timer, so the page behind the hero drifts while the hero itself is correct.
PageBackdrop points that layer at the featured item and keeps it in step. It
reuses the exact URL the active slide already requested through
SlideCreator.buildImageUrl, so the browser serves the image from cache and no
extra request is made.
Two details worth calling out:
Jellyfin's rotator adds its own .backdropImage siblings into the same container
and cycles them, which paints over anything we set. While the bar owns the
screen we keep our layer the only one and make sure it stays last. This is done
with a MutationObserver on the container rather than a timer, so it reacts to
the rotator instead of polling for it.
It is scoped to the home page. VisibilityObserver already knows when the bar is
hidden, so leaving the page hands the backdrop straight back to Jellyfin and no
other page is affected.
Defaults on, alongside slideAnimationEnabled and enableTrailers. Set
CONFIG.syncPageBackdrop to false to leave the backdrop alone.