Skip to content

feat: configurable media bar content source (collections/playlists)#43

Merged
RadicalMuffinMan merged 13 commits intoMoonfin-Client:masterfrom
enyineer:feature/custom-mediabar
Mar 15, 2026
Merged

feat: configurable media bar content source (collections/playlists)#43
RadicalMuffinMan merged 13 commits intoMoonfin-Client:masterfrom
enyineer:feature/custom-mediabar

Conversation

@enyineer
Copy link
Copy Markdown
Contributor

@enyineer enyineer commented Mar 8, 2026

Summary

Closes #39

Allows administrators and users to configure the media bar to display content from specific Jellyfin collections and/or playlists, instead of only random library items.

Features

  • Content Source selector — choose between "Library (Random)" (existing behavior) or "Collections / Playlists"
  • Collection/Playlist picker — multi-select with poster thumbnails and type badges (Collection vs Playlist)
  • Shuffle toggle — randomize item order from selected collections, or maintain sort order
  • Admin defaults — admins can set default source type, default collections, and shuffle preference for all users
  • Backward compatible — if nothing is configured, the existing random behavior is preserved
  • Settings sync — new settings sync across devices like all other Moonfin settings

Changes

Backend

  • MoonfinSettingsProfile.cs — added MediaBarSourceType, MediaBarCollectionIds, MediaBarShuffleItems
  • MoonfinUserSettings.cs — added matching legacy v1 fields for migration
  • MoonfinSettingsService.cs — added reset entries in ClearLegacyFields
  • configPage.html — added admin default controls (source type dropdown, collection picker, shuffle toggle)

Frontend

  • api.js — added getCollectionsAndPlaylists() and getCollectionItems() methods
  • mediabar.jsloadContent() branches on source type; applySettings() tracks new settings
  • settings.js — source dropdown, collection picker UI, shuffle toggle, loadCollectionPicker() method
  • storage.js — defaults + server↔local sync mappings for 3 new settings
  • settings.css — collection picker styles

Screenshots

User Settings — Default (Random)

user-settings-default-random

User Settings — Collections / Playlists

user-settings-mediabar-collections-playlists

Admin Settings — Default Collections / Playlists

admin-settings-collections-playlists

…and an expanded configuration interface across frontend and backend.

Signed-off-by: enyineer <nico.enking@gmail.com>
@enyineer
Copy link
Copy Markdown
Contributor Author

enyineer commented Mar 8, 2026

Hey! Just a heads-up — this PR includes some formatting/whitespace changes alongside the actual feature work. These came from the IDE auto-formatter running on files that were touched during development (e.g. consistent indentation, trailing spaces, quote style normalization in JS).

They don't affect functionality at all, but I understand they add noise to the diff. If you'd prefer to keep those out and only have the feature-related changes, just let me know and I'll revert the formatting to match the original style. Happy either way!

@broken-droid
Copy link
Copy Markdown

For a library option, I think people were also interested in picking which libraries to use. It'd probably be convenient to have a synced setting for selected library ids that all clients can use. --Just imo, but maybe someone else can chime in.

@enyineer
Copy link
Copy Markdown
Contributor Author

Great idea, I'll look into that at Sunday (maybe earlier if I have time for it).

Would be interested in Axl's opinion though.

…abar

# Conflicts:
#	frontend/src/components/settings.js
#	frontend/src/utils/storage.js
@enyineer
Copy link
Copy Markdown
Contributor Author

image

What do you think about this? Should we remove the "Content Type" in favor of picking actual libraries or leave it as complimenting feature? I'm unsure, because they both kind of do the same while being different filters. Having both might be confusing.

Allow users to optionally select specific Jellyfin libraries when using
Library (Random) mode. When no libraries are selected, all libraries are
used (preserving existing default behavior).

- Add mediaBarLibraryIds to backend models and settings service
- Extend getRandomItems() API to support parentId filtering per library
- Add library picker UI in user settings and admin config page
- Reuse collection picker styling for consistent UX
@enyineer
Copy link
Copy Markdown
Contributor Author

The failing CI is because of a permissions issue that will be fixed by #48

I'll update this branch as soon as #48 is merged so the build can run again.

@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

I agree with @broken-droid it should be per-library and we can build out an endpoint that the tv and mobile clients can use as well but I can handle that part

@broken-droid
Copy link
Copy Markdown

What do you think about this? Should we remove the "Content Type" in favor of picking actual libraries or leave it as complimenting feature? I'm unsure, because they both kind of do the same while being different filters. Having both might be confusing.

I like it. I think it makes sense to keep content type too. You don't really know how everyone has their libraries set up.

@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

Sorry was out and about doing errands, looing at #48 now

@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

The main thing I get asked for lately is local trailers and per-library for the media bar

- Remove mediaBarContentType from backend models, service, frontend
  storage/settings/mediabar/api, and README
- Add GET /Moonfin/MediaBar?profile= endpoint that resolves user settings
  and queries ILibraryManager for media bar items server-side
- Frontend calls unified endpoint first with client-side fallback
- Update README with new library selection settings and MediaBar endpoint
@enyineer
Copy link
Copy Markdown
Contributor Author

enyineer commented Mar 14, 2026

Changes in this update

Removed: mediaBarContentType setting

Per your feedback on preferring actual library selection over content type filtering, the mediaBarContentType setting has been removed from:

  • Backend models (MoonfinSettingsProfile.cs, MoonfinUserSettings.cs)
  • Settings service (ClearLegacyFields)
  • Frontend: storage mappings, settings UI, media bar component, and API utility

This is a BREAKING CHANGE! Existing settings for "Content Type" will be lost. However, it is better to only have one filter IMO because it might be confusing if users accidentally select "Shows only" while also selecting a Library that only contains Movies yields 0 results.

New: GET /Moonfin/MediaBar endpoint

A new server-side endpoint that serves media bar content directly. This enables any client (Android, TV, etc.) to fetch resolved media bar items with a single call — no need to reimplement the settings resolution + library query logic on each platform.

How it works:

  • Resolves user settings per device profile (desktop, mobile, tv, global)
  • Queries ILibraryManager for movies/series (library source) or collection items
  • Returns items in a shape compatible with Jellyfin's BaseItemDto (same property names: Id, Name, ImageTags, BackdropImageTags, etc.)
  • Supports library filtering via mediaBarLibraryIds and shuffle via mediaBarShuffleItems
GET /Moonfin/MediaBar?profile=desktop
Authorization: MediaBrowser Token="..."

The web frontend already uses this endpointmediabar.js now calls it first with a fallback to the previous client-side logic if unavailable. Adding support to Android/TV clients should be straightforward since the response format matches Jellyfin's native item API.

Fixes

  • Settings dialog profile resolution: createDialog was resolving settings for the physical device profile instead of the active edit profile, causing stale device-level overrides to mask global changes. Fixed by using Storage.getAll(Storage.getActiveEditProfile()).
  • Cross-version compatibility: The endpoint uses only stable BaseItem APIs (no IDtoService, IUserManager, or SortOrder which changed between Jellyfin 10.10 and 10.11). Shuffling is done in-memory.
  • Double MediaBar request: Initialized content-tracking state in init() to prevent a redundant reload from the sync event.

…diabar

# Conflicts:
#	frontend/src/components/mediabar.js
#	frontend/src/utils/api.js
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 14, 2026

✅ Build Successful

The plugin compiled successfully against .NET 8 / Jellyfin 10.10.0.

Property Value
Commit 8505f15
Workflow Build #16

@enyineer enyineer marked this pull request as draft March 14, 2026 09:31
@enyineer
Copy link
Copy Markdown
Contributor Author

There's still a bug with Collections / Playlists in the new endpoint return 0 results. I'll investigate.

@enyineer
Copy link
Copy Markdown
Contributor Author

enyineer commented Mar 14, 2026

Fix: Collections/Playlists in MediaBar endpoint

The GetCollectionItems method was initially using InternalItemsQuery with ParentId, which returned 0 items because Jellyfin BoxSets use LinkedChildren (not parent-child hierarchy).

Subsequent fixes using Folder.GetChildren() and ILibraryManager.GetItemList() both hit MissingMethodException due to Jellyfin 10.10→10.11 API changes (the User type parameter changed).

Final approach avoids all unstable method signatures:

  1. GetItemById() to fetch the collection (stable across versions)
  2. Cast to Folder (type check only, no method call)
  3. Iterate folder.LinkedChildren (data property — no method resolution)
  4. GetItemById() per linked child to resolve items (stable across versions)

This keeps the plugin compatible with both Jellyfin 10.10 and 10.11+.

@enyineer enyineer force-pushed the feature/custom-mediabar branch from 59a9d4f to e4bed26 Compare March 14, 2026 09:41
@enyineer enyineer marked this pull request as ready for review March 14, 2026 09:46
@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

Hey! Just a heads-up — this PR includes some formatting/whitespace changes alongside the actual feature work. These came from the IDE auto-formatter running on files that were touched during development (e.g. consistent indentation, trailing spaces, quote style normalization in JS).

They don't affect functionality at all, but I understand they add noise to the diff. If you'd prefer to keep those out and only have the feature-related changes, just let me know and I'll revert the formatting to match the original style. Happy either way!

please do, it makes it a bit harder to review the PR

Comment thread backend/Api/MoonfinController.cs Outdated
Comment thread backend/Api/MoonfinController.cs Outdated
Comment thread backend/Api/MoonfinController.cs Outdated
Comment thread backend/Api/MoonfinController.cs Outdated
@enyineer
Copy link
Copy Markdown
Contributor Author

Thanks for your good review comments. Will look at it ASAP (either in a few hours or tomorrow). I'm not at home currently.

@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

Eagerly waiting so I can push this update out and start working on the other apps too

@enyineer
Copy link
Copy Markdown
Contributor Author

Give me 20 minutes or so ;-)

@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

countdown

…ormatting revert

- Replace new Random() with Random.Shared via shared ShuffleAndTake() helper
- Implement partial Fisher-Yates: O(limit) instead of O(n) for shuffle
- Add .editorconfig, .prettierrc, .prettierignore for consistent formatting
- Reformat JS files with prettier to match upstream single-quote/4-space style
- Re-indent configPage.html to match upstream 4-space style
- Revert all formatting-only changes from previous commits
@enyineer
Copy link
Copy Markdown
Contributor Author

Changes addressing review comments

✅ Implemented

Random.Shared + shared helper — Replaced all 3 new Random() instances with a single ShuffleAndTake() helper using Random.Shared (thread-safe, zero allocation).

Partial Fisher-Yates shuffle — When limit < allItems.Count, only limit swaps are performed instead of shuffling the entire list. Each swap picks from position i to the end of the full list, so every item has equal probability. O(limit) instead of O(n) — for a 5,000 item library with limit=10, that's 10 swaps instead of 4,999.

Formatting revert — Added .editorconfig, .prettierrc, and .prettierignore to enforce consistent formatting going forward. Ran prettier on all frontend JS files and re-indented configPage.html to match the existing 4-space/single-quote style. The diff went from ~8,700 changed lines down to ~2,700.

❌ Cannot implement (cross-version compatibility)

User-aware InternalItemsQuery(user) and DB-side OrderBy = Random — Both require the User object from IUserManager.GetUserById(). The issue:

  • Jellyfin 10.10: User type lives in Jellyfin.Data.Entities.User
  • Jellyfin 10.11: User type moved to Jellyfin.Database.Implementations.Entities.User

Since this plugin targets ABI 10.10.0, calling InternalItemsQuery(user) would throw MissingMethodException at runtime on Jellyfin 10.11 because the constructor signature references a different User type than what the 10.11 runtime provides. The same applies to SortOrder which also changed namespaces. Once the plugin drops 10.10 support, both suggestions become viable.

Multiple parent IDsInternalItemsQuery exposes ParentId (singular Guid), not a multi-parent query. Recursive = true is required because Jellyfin libraries are virtual folders — movies/series are nested children, not direct children. Without it, queries return 0 results. The loop over library IDs is fine since users typically have 1–5 libraries.

Remaining formatting noise

There's still some formatting diff in the JS files (~35-50% of their line counts). This is because our previous commits used double quotes and mixed 2-space indentation, while the codebase uses single quotes with 4-space indentation. Prettier normalized these to match. The changes are purely cosmetic (verified by stripping whitespace+quotes and comparing — the logic is byte-identical).

Is this remaining quote/indent normalization noise acceptable, or would you prefer we strip it out further? Happy to split the formatting into a separate commit if that helps with review.

@enyineer
Copy link
Copy Markdown
Contributor Author

enyineer commented Mar 14, 2026

Give me another 10 minutes to test please before reviewing and merging.

Edit: manual testing looks good. Requesting review.

@enyineer
Copy link
Copy Markdown
Contributor Author

enyineer commented Mar 14, 2026

Additional note on the user-aware query (security fix)

To clarify — the suggestion to use new InternalItemsQuery(user) for user-aware library visibility is a valid security concern. Without it, the MediaBar endpoint could return items from libraries a user doesn't have permission to see (e.g. parental rating restrictions, hidden libraries).

However, implementing this requires the User object from IUserManager.GetUserById(). The User type moved between Jellyfin versions:

  • 10.10: Jellyfin.Data.Entities.User
  • 10.11: Jellyfin.Database.Implementations.Entities.User

Since the plugin currently targets ABI 10.10.0, using InternalItemsQuery(user) compiles fine but crashes with MissingMethodException at runtime on Jellyfin 10.11, because the runtime expects a different User type than what the DLL was compiled against.

To implement this fix directly, we would need to change the ABI target from 10.10 to 10.11, which would make the plugin incompatible with Jellyfin 10.10 installations. The same applies to DB-side OrderBy = ItemSortBy.Random (where SortOrder also changed namespaces).

Possible workaround: dynamic keyword

There is a C# workaround that could let us keep 10.10 compatibility while still applying user permissions:

var user = userManager.GetUserById(userId);
var query = new InternalItemsQuery(); // parameterless (safe)
if (user != null)
{
    ((dynamic)query).User = user; // resolved at runtime by property name, not type
}

Using dynamic bypasses compile-time type checking and resolves the User property by name at runtime. Since the property exists in both 10.10 and 10.11 (same name, same semantics), this should work on both versions.

Cons of the dynamic approach:

  • No compile-time safety — if Jellyfin ever renames or removes the User property, it fails silently at runtime instead of at compile time
  • Slightly slowerdynamic uses the DLR (Dynamic Language Runtime) which adds overhead on the first call, though it's cached after that
  • SetUser() not called — setting the User property directly skips the SetUser() method that also applies MaxParentalRating, BlockUnratedItems, and ExcludeInheritedTags. We'd need to call SetUser via dynamic too, or set those properties manually
  • Harder to maintain — future developers won't get IDE autocomplete or refactoring support for dynamic calls

Would you like us to try this approach, change the ABI target to 10.11+, or keep 10.10 compatibility as-is for now?

@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

I really would prefer the prettier files removed and the formatting noise removed.

@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

To the point where I wont approve this unless it's removed.

@enyineer
Copy link
Copy Markdown
Contributor Author

Working on it. Can't cherry pick because it's one commit.

…nd shuffle

- Replace mediaBarContentType with mediaBarSourceType (library/collection)
- Add server-side MediaBar endpoint with client-side fallbacks
- Add collection/library picker UI in settings panel
- Add shuffle toggle for collection items
- Add admin default settings for source type, collections, libraries
- Remove prettier config files
- Restore upstream formatting in settings.css
@enyineer
Copy link
Copy Markdown
Contributor Author

Tried to remove all formatting noise, my local tests are still fine. I hope I didn't miss anything, but it looks good to me.

Sorry for the hassle.

@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

no worries, this just looks cleaner and smaller commit to go over. Let me look now. Did you also see the comments @broken-droid left earlier?

@enyineer
Copy link
Copy Markdown
Contributor Author

enyineer commented Mar 15, 2026

Yes, but as mentioned, we cannot fix the security issue without using dynamic (which leads to less compile time safety) or updating the ABI to 10.11.0.

Would like to have your opinion on this.

Updating the ABI would allow other performance optimizations @broken-droid mentioned, removing the need to do some shuffling and other stuff in memory, but that would exclude any Jellyfin 10.10. Users from using this Plugin.

Comment thread backend/Pages/configPage.html Outdated
Comment thread backend/Pages/configPage.html Outdated
Comment thread frontend/src/components/settings.js Outdated
Comment thread frontend/src/components/settings.js Outdated
Comment thread backend/Api/MoonfinController.cs Outdated
Comment thread frontend/src/components/mediabar.js
Comment thread backend/Api/MoonfinController.cs
@broken-droid
Copy link
Copy Markdown

would either of these work for multiple parentids, or is that another restriction

        AncestorIds = Array.Empty<Guid>();
        TopParentIds = Array.Empty<Guid>();

…ges, filter backend item processing to movies and series, and ensure the mediabar is hidden when not on the home page.

Signed-off-by: enyineer <nico.enking@gmail.com>
Shuffling is handled client-side. Removes ShuffleAndTake method,
shuffle parameter from GetCollectionItems, and shuffle variable
from GetMediaBarItems. Keeps MediaBarShuffleItems setting for
client-side use.
…immediately if no sync is performed.

Signed-off-by: enyineer <nico.enking@gmail.com>
…with robust random ordering via reflection.

Signed-off-by: enyineer <nico.enking@gmail.com>
@enyineer
Copy link
Copy Markdown
Contributor Author

would either of these work for multiple parentids, or is that another restriction

        AncestorIds = Array.Empty<Guid>();
        TopParentIds = Array.Empty<Guid>();

Changed the usages as suggested. Please review.

@RadicalMuffinMan RadicalMuffinMan merged commit 20b6ac2 into Moonfin-Client:master Mar 15, 2026
1 check passed
@enyineer enyineer deleted the feature/custom-mediabar branch March 15, 2026 01:57
@enyineer
Copy link
Copy Markdown
Contributor Author

Thanks for merging and I'm very sorry for the mess in this PR. Hope the next one will be cleaner again. I'll remember to not change any formatting.

@RadicalMuffinMan
Copy link
Copy Markdown
Contributor

No worries, just don't just take whatever AI gives you at face value and commit or comment it. Read the code and understand it too otherwise it becomes a headache for everyone involved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhancment for media bar

3 participants