* feat: added playlist from queue creation
- added functionality to create a playlist from queue
- prefilling is done as an extra function to be api agnostic since navidrome native api for example does not offer a parameter for filling a playlist with songs on creation
#2204
* fix: fixed wrong declaration
An unset audio device id means "follow the system default" (setSinkId is
skipped and mpv uses audio-device=auto), but the Audio device select in
the player settings popover and the playback settings page rendered
blank in that state. Make both selects controlled and fall back to the
enumerated default entry (browser 'default' / mpv 'auto') so the device
actually in use is always displayed. The stored setting stays unset, so
playback keeps following the OS default until a device is explicitly
chosen; clearing the select returns to the default entry instead of
going blank.
Small change that modifies the `scrobble` controller method to always limit sending a
`scrobble` payload to the subsonic backend to cases where a scrobble is being submitted
or a new song is being started.
This is the current behavior for subsonic backends which support the `reportPlayback` backend
(e.g., Navidrome). This change only modifies the behavior for other backends, which would receive
`scrobble` notifications whenever a song changed for both the stopped song and the newly-started song.
After this change, all subsonic backends now only receive a `scrobble` when a new song is started or
when feishin is attempting to submit a scrobble.
* feat(player): add right-click audio output device menu to volume icon
Right-clicking the volume/speaker icon in the player bar now opens a
context menu listing available audio output devices, with the active
device checkmarked and a System default reset option.
Selecting a device writes to the existing audioDeviceId (web) or
mpvAudioDeviceId (mpv) playback settings, so the choice is applied live
and persisted across restarts via the settings store. Unlike the
settings-page selector, the menu is not disabled during playback.
* refactor(player): drop redundant audio menu label, document wrapper div
Address review feedback: remove the self-explanatory ContextMenu.Label
from the audio output menu, and add a comment explaining why the volume
icon is wrapped in a div (Mantine Tooltip does not forward Radix's
asChild onContextMenu/ref to the button).
applyStartupSeek() compared the armed target song against
getQueue().items[player.index], but player.index is a position in the
shuffled order while getQueue().items is always the default order. With
shuffle enabled the lookup resolves to the wrong song, the uniqueId
check fails, and the startup resume-seek is silently cancelled — the
track then plays from 0:00 and the progress poller overwrites the
persisted timestamp.
Use getCurrentSong() instead, which maps the index through
queue.shuffled, matching how the seek target is armed.
Fixes#2202
Co-authored-by: Maurits <WhoCarrot@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Romaji conversion joined all synced lyric lines into one string. Because the block contained kana somewhere, hasKana passed for the entire array of lyrics.
mpv/ffmpeg had no network-level timeout or reconnect options, so a
network stream left open across a system sleep would block forever on
the now-dead TCP connection instead of failing or reconnecting. Since
Node-MPV's IPC commands only resolve when mpv replies, a wedged mpv
process also made quit()/restart hang indefinitely, so the only way
out was to kill the whole app.
- Add --network-timeout and ffmpeg reconnect options to mpv's default
parameters so a stalled stream fails fast instead of hanging.
- Make the quit() helper resilient to an unresponsive mpv process by
racing it against a timeout and force-killing as a fallback.
- Listen for Electron's powerMonitor 'resume' event and tell the
renderer to reload mpv, so playback recovers automatically instead
of requiring a manual app restart.
* Fixed bad smart playlist field s
* first try to add playlist highlight
* Simplified calls
* Now works for grids too.
* Derive the playlist highlight from the currently-playing track's origin instead of a stale global field.
* addressed comments
The share URL is only known after the create-share request resolves, so the
previous navigator.clipboard.writeText() in the mutation onSuccess callback ran
outside the originating click's user activation. Firefox and Safari reject such
writes ('Clipboard write was blocked due to lack of user activation'), so the
link was never copied (and the toast still claimed success).
Issue clipboard.write() synchronously inside the submit gesture with a
ClipboardItem whose text/plain value is a promise resolving to the share URL,
which preserves the user activation while the share is created. Falls back to
writeText, and finally to the existing 'click to open' toast when the clipboard
is unavailable or blocked, with the toast text reflecting whether the copy
actually succeeded.
Co-authored-by: ilusha <ilusha.basic@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
- path replacement during runtime instead of during API normalization
- fix Navidrome API path not appending libraryPath which caused inconsistency between ND and Subsonic paths
* feat(artists): preserve artist detail scroll position on back navigation
* fix(artists): target OverlayScrollbars viewport child for scroll persistence
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
---------
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
keyboardCodeToHotkeyKey had branches for Key*, Digit*, and Numpad* codes
but none for function keys, so KeyboardEvent.code values "F1".."F24"
returned null and the hotkey capture handler silently dropped them. This
made every function key unassignable (not just F13+ such as F21/F22).
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: preserve infinite list cache on component remount
When browsing with random sort, navigating to a detail view and coming
back would reshuffle the list. This happens because the list component
unmounts, losing its internal ref guard, and the reset effect re-fetches
all pages — returning a new random order from the server.
* fix(player): step correctly when seeking rapidly
mediaSkipBackward/mediaSkipForward compute the new target relative to
the timestamp store, which is only refreshed by a ~500ms engine poll.
Rapid presses within that window all read the same stale position and
recompute the same target, so the time appears stuck until the poll
catches up.
Update the timestamp store immediately when a seek is issued so
subsequent presses compute from the new position; the poll then just
reconciles to the same value. Also applied to mediaSeekToTimestamp so
absolute seeks reflect in the UI without the poll lag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(player): unify seek comments per review
Use one canonical explanation in mediaSkipBackward and have
mediaSeekToTimestamp/mediaSkipForward reference it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: better handling of custom font
Practically speaking, custom font seems to have only worked on Linux, because
`net.fetch` would include the mime type in the response headers which could validate the payload.
This doesn't appear to be the case on windows/macOS. Instead:
1. On Linux (or if some other system supports it), check the content type. If good, serve as normal
2. Otherwise, fetch the payload. Read the first four to five bytes and check for a valid magic number.
Additionally, to prevent arbitrary requests fetching other paths via injected content, sync the custom font path
to the main process, and then make _every_ request to `feishin:/` point to the same renderer path.
When setting the font, first send the path to the main process. This will register `feishin:/` to point
to the path provided. This is done via a promise-based set.
Finally, provide a default value for the file input (a best effort approximation for the last part of the file path)
on the file input component.
* make the linter happy
The command-palette-specific IPC approach didn't cover other modals
with inputs (settings search, playlist creation, etc.). Replace it
with document-level focusin/focusout listeners that signal the main
process whenever any input/textarea/contenteditable gains or loses
focus, so the menu rebuild is triggered automatically for all current
and future input surfaces.
Co-authored-by: muckymucky <muckymucky@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
On macOS, menu item accelerators (e.g. Space for Play/Pause) fire even
when an input has focus, bypassing the renderer-side useHotkeys guard
added in #1925. Typing a space character in the command palette search
toggled play/pause instead of inserting a space.
Track command palette open state in the main process via IPC. When the
palette is open, rebuild the application menu with empty playback
accelerators so single-key shortcuts no longer intercept keystrokes
intended for the search input. Restore accelerators when the palette
closes.
Co-authored-by: Jack McCrea <jack@MacBookPro.lan>
- switch to single web player instance for loop instead of dual-player
- this fixes the issue, but does have a breaking change if using the crossfade player
- supports song, album, artist, and album artist tables
- hovering over the first row index or track number column will display a hovercard for the playback controls
* fix(player): stop radio before starting track playback
When internet radio is streaming, clicking a track to play does nothing
because the MPV engine guards check currentStreamUrl and bail early.
Stop the radio stream before setting up the new queue on Play.NOW and
Play.SHUFFLE so the audio engine proceeds normally.
Fixes#2038
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct import ordering for lint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Group playlists into folders using a configurable separator (default '/').
Three view modes:
- Single: first-level grouping only (original behavior)
- Tree: full recursive nesting with connecting lines (configurable indent and line color)
- Navigation: drill-down view with stacked breadcrumb chain
Folders are sorted before playlists at every level. New settings render as
indented sub-options under the master 'Enable folders' toggle.
* made sidebar image just use flex
* force aspect ratio to be square
* prevent image container from expanding
---------
Co-authored-by: jeffvli <jeffvictorli@gmail.com>
The setDisplayMediaRequestHandler was calling desktopCapturer.getSources()
to provide a video source that the renderer never uses (it requests
video: false and only consumes audio tracks). On Wayland, this created a
new xdg-desktop-portal ScreenCast session on every launch, showing an
unavoidable screen share dialog because Electron does not persist
PipeWire restore tokens across desktopCapturer sessions.
Simplified the handler to return only { audio: 'loopback' }, which
captures system audio via PipeWire/PulseAudio monitor source without
any portal interaction.
Ref: effvli/feishin#1919 - tl;dr: Button actually reloads/refreshes
lyrics info from the server too, it makes it, well, clearer what it does
in that case - allows to reread lyrics from server without clearing whole cache.
Handlers were being registered and destroyed on state change/re-render,
causing media controls to vanish during rapid use or quick track skipping.
Persist handlers and add debounce for rapid track skipping.
Tested on Windows, Linux, and Android.
1. MPRIS (or `mpris-service`) is very fragile. If an invalid `mpris:trackid` (something with `-` or spaces) is passed in, it breaks. Use a minimal track id instead
2. Only populate album/artist/title
* fix: glassy dark content container claiming entire width (#1713)
* fix: apply container height fix only when using window bar
---------
Co-authored-by: Jeff <42182408+jeffvli@users.noreply.github.com>
* feat(playlist): support updating playlist track order
* force track mode when editing
* use common confirmation for save
* remove en editPLaylist key
electron-builder added compatibility with the AppImage Type2 runtime
(i.e. FUSE-less runtime). However, this is - for now anyway - an opt-in
feature, which this commit enables.
- chrome (and other browsers) determine that the audio element is inactive if the volume is set to 0 when paused, leading to the resume (play) mediasession event to no longer be available
Also includes exports from SVG for all relevant files:
- `assets/icons/` in 16-1024px size PNGs
- `assets/icons/icon.ico` using same sizes as the old one
- `assets/icons/` IconTemplate
- `media/` black and white logo-only variants
- `resource/icon.png`
- removes complex lyrics fetch and override logic, and instead uses a single query as a source of truth for the lyrics
- properly handles loading state, invalidation, and refetch
settings.js (which injects SERVER_URL into the browser) was served
without Cache-Control headers, causing Cloudflare and other reverse
proxies to cache the old value indefinitely. Additionally, when
SERVER_LOCK is enabled, the persisted server URL in localStorage was
never compared against the current window.SERVER_URL, so same-browser
sessions kept using the old server even after settings.js was updated.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- some older subsonic servers used numeric ids which causes the rootFolderId regex to fail which resulted in the getFolder endpoint to always attempt to fetch the root
* improve album artist favorite performance and search
* adjust top songs / favorite songs sections
---------
Co-authored-by: jeffvli <jeffvictorli@gmail.com>
- this query in some cases can return a very large amount of data, depending on the size of the user's library
- increasing the cacheTime reduces the frequency of fetches while disabling structuralSharing reduces the need for react-query to do a deep equality comparison for the cache
* feat: add sleep timer to player bar
- Add sleep timer button in player bar right controls
- Preset options: End of song, 5/10/15/30/45 min, 1 hr, 2 hrs
- Custom timer with HH:MM:SS input fields
- Timer only counts down while music is playing
- Timer pauses playback when it expires
- End-of-song mode pauses at the next track change
- Uses theme-aware styling (--theme-colors-surface)
- Add sleepTimer/sleepTimerOff icons (LuTimer/LuTimerOff)
- Add i18n strings for sleep timer UI
---------
Co-authored-by: York <york@BonecharMac.local>
Co-authored-by: jeffvli <jeffvictorli@gmail.com>
Updated by "Cleanup translation files" hook in Weblate.
Translated using Weblate (French)
Currently translated at 90.7% (1034 of 1139 strings)
Translated using Weblate (Ukrainian)
Currently translated at 29.0% (331 of 1139 strings)
Translated using Weblate (Danish)
Currently translated at 100.0% (1139 of 1139 strings)
Translated using Weblate (Ukrainian)
Currently translated at 26.5% (302 of 1139 strings)
Translated using Weblate (Ukrainian)
Currently translated at 22.0% (251 of 1139 strings)
Translated using Weblate (Danish)
Currently translated at 90.0% (1026 of 1139 strings)
Translated using Weblate (Danish)
Currently translated at 28.1% (321 of 1139 strings)
Translated using Weblate (Ukrainian)
Currently translated at 9.5% (109 of 1139 strings)
Translated using Weblate (Ukrainian)
Currently translated at 5.7% (66 of 1139 strings)
Translated using Weblate (Czech)
Currently translated at 100.0% (1139 of 1139 strings)
Translated using Weblate (Czech)
Currently translated at 99.7% (1136 of 1139 strings)
Translated using Weblate (Danish)
Currently translated at 4.8% (55 of 1139 strings)
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1139 of 1139 strings)
Translated using Weblate (Danish)
Currently translated at 0.4% (5 of 1139 strings)
Translated using Weblate (Spanish)
Currently translated at 100.0% (1139 of 1139 strings)
Translated using Weblate (Polish)
Currently translated at 99.9% (1138 of 1139 strings)
Translated using Weblate (Czech)
Currently translated at 100.0% (1135 of 1135 strings)
Translated using Weblate (Dutch)
Currently translated at 99.8% (1133 of 1135 strings)
Translated using Weblate (Dutch)
Currently translated at 84.4% (959 of 1135 strings)
Translated using Weblate (Polish)
Currently translated at 100.0% (1135 of 1135 strings)
Translated using Weblate (Spanish)
Currently translated at 100.0% (1135 of 1135 strings)
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1135 of 1135 strings)
Translated using Weblate (Spanish)
Currently translated at 100.0% (1131 of 1131 strings)
Translated using Weblate (German)
Currently translated at 78.9% (893 of 1131 strings)
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (1131 of 1131 strings)
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 92.7% (1047 of 1129 strings)
Translated using Weblate (Polish)
Currently translated at 100.0% (1129 of 1129 strings)
Translated using Weblate (Spanish)
Currently translated at 100.0% (1129 of 1129 strings)
Translated using Weblate (Japanese)
Currently translated at 85.1% (961 of 1129 strings)
Translated using Weblate (Catalan)
Currently translated at 100.0% (1129 of 1129 strings)
Translated using Weblate (Polish)
Currently translated at 100.0% (1129 of 1129 strings)
Co-authored-by: Alexander Welsing <kontakt@a-wels.de>
Co-authored-by: Denisa Alicia Rissa <denisarissa@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
Co-authored-by: Ondo <SparkyOndo@proton.me>
Co-authored-by: York <goog10216922@gmail.com>
Co-authored-by: Yurii <04_hours.lambing@icloud.com>
Co-authored-by: bokse <weblate@bokse.nl>
Co-authored-by: haha4ni <haha4ni@hotmail.com>
Co-authored-by: karigane <169052233+karigane-cha@users.noreply.github.com>
Co-authored-by: skajmer <skajmer@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/da/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/de/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nl/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/uk/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/
Translation: feishin/Translation
The CommandPalette component was being rendered twice when in mobile view:
1. In ResponsiveLayout via LayoutHotkeys (which handles all layouts)
2. In MobileLayout directly
This caused two overlapping command menus to open when pressing Ctrl+K
in mobile view, with keyboard input going to the background menu.
The fix removes the duplicate CommandPalette from MobileLayout since
LayoutHotkeys already provides it for all layouts (both desktop and mobile).
Fixes#1666
Co-authored-by: Kai Gritun <kai@kaigritun.com>
- Add `handleScrobbleFromRepeat` callback to reset scrobble state and send 'start' event when player repeats a song, ensuring accurate scrobbling in repeat mode.
- Fix typo in `web.vite.config.ts` by correcting '@tanstack_react-query-persist-client' to '@tanstack/react-query-persist-client' for proper package reference.
* implement theme
* refactor theme stylesheets to load inline to simplify vite bundling
* add missing css module scope name for web build
---------
Co-authored-by: jeffvli <jeffvictorli@gmail.com>
* Prevent double fetching when force refreshing paginated views
* remove await from infinite list loader query invalidation
* add mutation and loading state to list refresh
* add non-suspense query to list genre filters to add loading state
* remove list count data set on random queries
---------
Co-authored-by: jeffvli <jeffvictorli@gmail.com>
Currently translated at 85.8% (962 of 1120 strings)
Translated using Weblate (Polish)
Currently translated at 100.0% (1120 of 1120 strings)
Translated using Weblate (Czech)
Currently translated at 100.0% (1120 of 1120 strings)
Translated using Weblate (Spanish)
Currently translated at 100.0% (1120 of 1120 strings)
Translated using Weblate (Polish)
Currently translated at 100.0% (1119 of 1119 strings)
Translated using Weblate (Japanese)
Currently translated at 85.3% (955 of 1119 strings)
Translated using Weblate (Russian)
Currently translated at 67.1% (751 of 1119 strings)
Translated using Weblate (Basque)
Currently translated at 78.0% (869 of 1114 strings)
Translated using Weblate (Basque)
Currently translated at 77.9% (868 of 1114 strings)
Translated using Weblate (Japanese)
Currently translated at 84.0% (936 of 1114 strings)
Translated using Weblate (Spanish)
Currently translated at 100.0% (1114 of 1114 strings)
Translated using Weblate (Japanese)
Currently translated at 83.9% (935 of 1114 strings)
Translated using Weblate (Catalan)
Currently translated at 100.0% (1114 of 1114 strings)
Translated using Weblate (Czech)
Currently translated at 100.0% (1114 of 1114 strings)
Translated using Weblate (Polish)
Currently translated at 100.0% (1114 of 1114 strings)
Translated using Weblate (Czech)
Currently translated at 97.3% (1084 of 1114 strings)
Translated using Weblate (Danish)
Currently translated at 0.3% (4 of 1114 strings)
Added translation using Weblate (Danish)
Translated using Weblate (Czech)
Currently translated at 100.0% (1114 of 1114 strings)
Translated using Weblate (Basque)
Currently translated at 84.5% (942 of 1114 strings)
Translated using Weblate (Spanish)
Currently translated at 100.0% (1114 of 1114 strings)
Translated using Weblate (Basque)
Currently translated at 84.5% (938 of 1110 strings)
Translated using Weblate (Basque)
Currently translated at 84.3% (936 of 1110 strings)
Translated using Weblate (Dutch)
Currently translated at 74.5% (828 of 1110 strings)
Translated using Weblate (Turkish)
Currently translated at 63.0% (700 of 1110 strings)
Translated using Weblate (French)
Currently translated at 92.9% (1032 of 1110 strings)
Translated using Weblate (Spanish)
Currently translated at 100.0% (1110 of 1110 strings)
Translated using Weblate (Czech)
Currently translated at 100.0% (1110 of 1110 strings)
Translated using Weblate (Catalan)
Currently translated at 100.0% (1110 of 1110 strings)
Translated using Weblate (Russian)
Currently translated at 72.0% (800 of 1110 strings)
Co-authored-by: Aitor Astorga <a.astorga.sdv@protonmail.com>
Co-authored-by: Denisa Alicia Rissa <denisarissa@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Fordas <fordas15@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Koray HATIRNAZ <hatirnazkoray@gmail.com>
Co-authored-by: KosmoMoustache <kosmomoustache@users.noreply.hosted.weblate.org>
Co-authored-by: Ondo <SparkyOndo@proton.me>
Co-authored-by: haha4ni <haha4ni@hotmail.com>
Co-authored-by: jay <jayma13222@gmail.com>
Co-authored-by: karigane <169052233+karigane-cha@users.noreply.github.com>
Co-authored-by: skajmer <skajmer@protonmail.com>
Co-authored-by: Роман <romkaeliseev@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ca/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/cs/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/da/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/es/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/eu/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/fr/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ja/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/nl/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/pl/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/ru/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/tr/
Translate-URL: https://hosted.weblate.org/projects/feishin/translation/zh_Hant/
Translation: feishin/Translation
* Added simple macOS dock menu similar to tray menu
* Enhanced and moved dock menu to darwin folder and enabled mpris on macOS to support play/pause state
* Added missing property sortName to silence TS error
This issue has been automatically marked as stale because it has not had recent activity. The resources of the Feishin team are limited, and so we are asking for your help.
If this is a **bug** and you can still reproduce this error on the <code>development</code> branch, please reply with all of the information you have about it in order to keep the issue open.
This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
stale-pr-message:>
This PR has been automatically marked as stale because it has not had recent activity. The resources of the Feishin team are limited, and so we are asking for your help.
This PR will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
@@ -59,7 +59,9 @@ For media keys to work, you will be prompted to allow Feishin to be a Trusted Ac
#### Linux Notes
We provide a small install script to download the latest `.AppImage`, make it executable, and also download the icons required by Desktop Environments. Finally, it generates a `.desktop` file to add Feishin to your Application Launcher.
Feishin is available in [Flathub](https://flathub.org/en/apps/org.jeffvli.feishin).
Alternatively, you can install it as an Appimage. We provide a small install script to download the latest `.AppImage`, make it executable, and also download the icons required by Desktop Environments. Finally, it generates a `.desktop` file to add Feishin to your Application Launcher.
Simply run the installer like this:
@@ -114,6 +116,7 @@ services:
- SERVER_LOCK=true# When true AND name/type/url are set, only username/password can be toggled
- SERVER_TYPE=jellyfin # the allowed types are:jellyfin, navidrome, subsonic. These values are case insensitive
- SERVER_URL=# http://address:port or https://address:port
- REMOTE_URL=# http://address or https://address
- LEGACY_AUTHENTICATION=false# When SERVER_LOCK is true, sets the legacy (plaintext) authentication flag for Subsonic/OpenSubsonic servers
- ANALYTICS_DISABLED=true# Set to true to disable Umami analytics tracking
ports:
@@ -134,7 +137,11 @@ services:
4._Optional_ - To hard code the server url, pass the following environment variables: `SERVER_NAME`, `SERVER_TYPE` (one of `jellyfin` or `navidrome` or `subsonic`), `SERVER_URL`. To prevent users from changing these settings, pass `SERVER_LOCK=true`. This can only be set if all three of the previous values are set. When `SERVER_LOCK=true`, you can also set `LEGACY_AUTHENTICATION=true` or `LEGACY_AUTHENTICATION=false` to configure the legacy authentication flag for the server (only applicable for Subsonic/OpenSubsonic servers).
5._Optional_ - To disable Umami analytics tracking in the Docker/web version, set the environment variable `ANALYTICS_DISABLED=true`. When enabled, the analytics script will not be loaded and all tracking will be disabled.
5._Optional_ - If your server uses a separate public-facing URL than what integrating applications use internally to communicate with your server, such as a separate Navidrome `ShareURL`, set `REMOTE_URL` to said public-facing URL.
6._Optional_ - To disable Umami analytics tracking in the Docker/web version, set the environment variable `ANALYTICS_DISABLED=true`. When enabled, the analytics script will not be loaded and all tracking will be disabled.
7._Optional_ - App settings (theme, language, sidebar options, etc.) can be overridden with environment variables on first run. The variables use the `FS_` prefix (e.g. `FS_GENERAL_THEME=defaultDark`, `FS_GENERAL_LANGUAGE=de`). See [the settings environment variable documentation](docs/ENV_SETTINGS.md) for the full list.
## FAQ
@@ -160,6 +167,9 @@ Feishin supports any music server that implements a [Navidrome](https://www.navi
- [Feishin fork by lux032](https://github.com/lux032/feishin) - Plex is not natively supported. Use the fork by lux032 to use Plex with Feishin.
### I have the issue "The SUID sandbox helper binary was found, but is not configured correctly" on Linux
This happens when you have user (unprivileged) namespaces disabled (`sysctl kernel.unprivileged_userns_clone` returns 0). You can fix this by either enabling unprivileged namespaces, or by making the `chrome-sandbox` Setuid.
Ubuntu 24.04 specifically introduced breaking changes that affect how namespaces work. Please see https://discourse.ubuntu.com/t/ubuntu-24-04-lts-noble-numbat-release-notes/39890#:~:text=security%20improvements%20 for possible fixes.
### How can I add custom themes?
On the desktop app, you can add custom themes by dropping JSON files into the Themes folder (Settings → General → Theme → Open Folder). See [the custom themes documentation](docs/CUSTOM_THEMES.md) for the file format and examples.
Custom themes let you add your own light and dark themes to the **desktop** app without rebuilding Feishin. Drop JSON theme files into the Themes folder; Feishin watches that folder and reloads themes when files change.
Custom themes are **Electron / desktop only**. They are not available in the web or Docker builds.
---
## Getting started
1. Open **Settings → General → Theme**.
2. Under **Custom Themes**, click **Open Folder** to reveal the Themes directory.
3. Add a `.json` file (for example `my-theme.json`).
4. Select the theme from the Theme dropdown (it appears under Dark or Light based on `mode`).
You can also click **Reload** in Settings if a change was not picked up automatically.
In development builds the folder name is under `feishin-dev` instead of `feishin`.
Theme files must be **`.json` files in the root of the Themes folder** (not nested in subfolders). Linked stylesheets may live next to them or in subfolders, as long as they stay inside Themes.
| `mantineOverride` | No | Partial [Mantine theme override](https://mantine.dev/theming/theme-object/). |
| `stylesheets` | No | Array of CSS file paths **relative to the Themes folder**. Contents are inlined when the theme is active. Paths that escape the Themes folder are ignored. |
Unspecified color and app values fall back to Feishin’s default theme (and then to anything provided by `extends`).
Values must be valid CSS colors recognized by Feishin’s color validator (for example `rgb(...)`, `rgba(...)`, `#rrggbb`, `#rgb`). Named CSS colors like `red` are rejected. Invalid entries are ignored and listed under Custom Themes warnings in Settings.
---
## App variables
Supported `app` keys (CSS values as strings):
| Key | Description |
|-----|-------------|
| `content-max-width` | Max width of main content |
For larger visual overrides (glass effects, layout tweaks, etc.), point `stylesheets` at CSS files inside the Themes folder:
```json
{
"mode":"dark",
"extends":"defaultDark",
"stylesheets":["overrides/my-theme.css"]
}
```
```text
Themes/
my-theme.json
overrides/
my-theme.css
```
Notes:
- Feishin watches **JSON** theme files for automatic reload. After editing only a CSS file, click **Reload** in Settings (or touch/save the `.json` file) so stylesheets are re-read.
- Empty or unreadable stylesheet paths are skipped with a console warning.
| Theme missing from the list | File must be `.json` in the Themes root; use **Reload**. |
| Theme shows an error in Settings | JSON is invalid or not a top-level object. Fix the file and reload. |
| Theme loads with a warning | One or more `colors` values were invalid and ignored. |
| CSS changes not applying | Edit/save the `.json` or click **Reload**; stylesheet watch is tied to JSON changes. |
| Extending does nothing / looks wrong | Confirm the `extends` id matches a built-in id or another custom filename (without `.json`). Avoid circular chains. |
Broken themes still appear in Settings with their error message so you can fix them without digging through logs.
# Environment variables for settings (web / Docker)
These variables override app settings **on first run** when no persisted settings exist. They are injected via `settings.js` (from `settings.js.template`) and only apply to the **web** build.
**Format:** All values are strings; booleans use `true`/`false`, numbers are numeric strings. Leave unset or empty to use the default.
| `general.sidebarPlaylistFolders` | `true` | `FS_GENERAL_SIDEBAR_PLAYLIST_FOLDERS` | `true` / `false` — Group playlists into folders by name separator. |
| `general.sidebarPlaylistFolderSeparator` | `/` | `FS_GENERAL_SIDEBAR_PLAYLIST_FOLDER_SEPARATOR` | Character or string that separates folder levels in a playlist name. Empty = use default. |
| `general.sidebarPlaylistFolderTreeIndent` | `16` | `FS_GENERAL_SIDEBAR_PLAYLIST_FOLDER_TREE_INDENT` | Pixels each tree level is indented (0–64). |
| `general.sidebarPlaylistFolderTreeLineColor` | *(empty)* | `FS_GENERAL_SIDEBAR_PLAYLIST_FOLDER_TREE_LINE_COLOR` | CSS color for tree connecting lines. Empty = theme default. |
| `general.sidebarPlaylistFolderView` | `tree` | `FS_GENERAL_SIDEBAR_PLAYLIST_FOLDER_VIEW` | `single` / `tree` / `navigation` — How folders are displayed in the sidebar. |
| `general.sidebarPlaylistList` | `true` | `FS_GENERAL_SIDEBAR_PLAYLIST_LIST` | `true` / `false` — Show playlist list in sidebar. |
style="display:inline;fill:url(#linearGradient2);stroke-width:25;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke"
id="background"
cx="256"
cy="256"
r="256"/><circle
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.19597;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke;filter:url(#filter249)"
d="M 220.84961,277.95117 183.5,315.59961 219.5,351.69922 239.5,332 c 0,0 5.85615,-6.19922 16.5,-6.19922 10.64385,0 16.5,6.19922 16.5,6.19922 l 20,19.69922 36,-36.09961 -37.34961,-37.64844 A 51.5,51.5 0 0 1 256,291.8125 51.5,51.5 0 0 1 220.84961,277.95117 Z"/><path
id="main"
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;paint-order:markers fill stroke;filter:url(#filter249)"
d="m 256,145.40039 c -7.11895,0 -13.56326,2.88552 -18.22852,7.55078 L 66.96875,323.46875 C 62.354158,328.08334 59.5,334.45837 59.5,341.5 c 0,14.08326 11.416739,25.5 25.5,25.5 7.04163,0 13.41666,-2.85416 18.03125,-7.46875 L 206.92578,255.93359 A 51.5,51.5 0 0 1 204.5,240.3125 a 51.5,51.5 0 0 1 51.5,-51.5 51.5,51.5 0 0 1 51.5,51.5 51.5,51.5 0 0 1 -2.42578,15.62109 L 408.96875,359.53125 C 413.58334,364.14585 419.95837,367 427,367 c 14.08326,0 25.5,-11.41674 25.5,-25.5 0,-7.04163 -2.85415,-13.41666 -7.46875,-18.03125 L 274.22852,152.95117 C 269.56326,148.2859 263.11895,145.40039 256,145.40039 Z"/></g></svg>
"smartPlaylist":"$t(entity.playlist, {\"count\": 1}) قائمة تشغيل ذكية",
"track_zero":"مقطع",
"track_one":"مقطع",
"track_two":"مقطعان",
"track_few":"مقاطع",
"track_many":"مقاطع",
"track_other":"مقاطع",
"song_zero":"أغنية",
"song_one":"أغنية",
"song_two":"أغنيتان",
"song_few":"أغاني",
"song_many":"أغاني",
"song_other":"أغاني",
"trackWithCount_zero":"{{count}} مقطع",
"trackWithCount_one":"{{count}} مقطع",
"trackWithCount_two":"{{count}} مقطعان",
"trackWithCount_few":"{{count}} مقاطع",
"trackWithCount_many":"{{count}} مقاطع",
"trackWithCount_other":"{{count}} مقاطع"
},
"error":{
"apiRouteError":"تعذّر توجيه الطلب",
"audioDeviceFetchError":"حصل خطأ أثناء محاولة الحصول على أجهزة الصوت",
"authenticationFailed":"فشلت المصادقة",
"badAlbum":"أنت ترى هذة الصفحة لأن هذه الأغنية ليست جزءاً من ألبوم. على الأرجح تظهر لك هذه المشكلة إذا كان لديك أغنية في المستوى الأعلى من مجلد الموسيقى. يقوم Jellyfin بتجميع الأغاني فقط إذا كانت داخل مجلد",
"credentialsRequired":"يتطلب بيانات اعتماد",
"genericError":"حدث خطأ",
"loginRateError":"تجاوزت الحد لمحاولات الدخول. حاول مجدداً بعد بضع ثوان",
"mpvRequired":"يتطلب MPV",
"multipleServerSaveQueueError":"قائمة التشغيل تحتوي على أغنية أو أكثر من خادم مختلف. هذا غير مدعوم",
"networkError":"حصل خطأ في الشبكة",
"noNetwork":"الخادم غير متوفر",
"noNetworkDescription":"تعذر الإتصال بالخادم",
"notificationDenied":"تم رفض أذن الإشعارات. هذا الإعداد لن يكون له أي تأثير",
"openError":"تعذر فتح الملف",
"playbackError":"حدث خطأ أثناء محاولة تشغيل الوسائط",
"playbackPausedDueToError":"تم ايقاف التشغيل بسبب خطأ",
"remoteDisableError":"حدث خطأ أثناء محاولة $t(common.disable) الخادم البعيد",
"remoteEnableError":"حدث خطأ أثناء محاولة $t(common.enable) الخادم البعيد",
"remotePortError":"حدث خطأ أثناء محاولة تعيين الخادم البعيد",
"remotePortWarning":"أعد تشغيل الخادم لتطبيق المنفذ الجديد",
"saveQueueFailed":"فشل حفظ قائمة التشغيل",
"serverLockSingleServer":"فقط خادم واحد متاح إذا الخادم مقفل",
"serverNotSelectedError":"لم يتم اختيار أي خادم",
"serverRequired":"يتطلب خادم",
"sessionExpiredError":"انتهت صلاحية جلستك",
"systemFontError":"حدث خطأ أثناء محاولة الحصول على خطوط النظام",
"settingsSyncError":"تم اكتشاف تعارضات بين إعدادات العارض والعملية الرئيسية. أعد تشغيل التطبيق لتطبيق التغييرات",
"invalidJson":"JSON غير صالح",
"invalidServer":"خادم غير صالح",
"localFontAccessDenied":"تم رفض الوصول إلى الخطوط المحلية"
"publicJellyfinNote":"لسبب ما، لا يكشف Jellyfin عما إذا كانت قائمة التشغيل عامة أم لا. إذا كنت ترغب في إبقائها عامة، يرجى التأكد من تحديد الخيار التالي"
"audioExclusiveMode_description":"حالت اختصاصی خروجی را فعال میکند. در این حالت، سامانه معمولاً قفل است و فقط mpv میتواند خروجی صدا دهد",
"audioExclusiveMode_description":"حالت اختصاصی خروجی را فعال میکند. در این حالت، سامانه معمولاً قفل است و فقط MPV میتواند خروجی صدا دهد",
"clearQueryCache_description":"یک 'پاکسازی نرم' از فیشین. این فهرستهای پخش و فرادادهی قطعهها را تازه میکند و متن شعرهای ذخیره شده را بازنشانی میکند. پیکربندیها، اعتبارنامههای سرویسدهنده و نگارههای کَش شده حفظ میشوند",
"clearCache_description":"یک 'پاکسازی سخت' فیشین. افزون بر پاکسازی کَش فیشین، کَش مرورگر هم تهی میشود (نگارههای ذخیره شده و باقی داراییها). اعتبارنامهها و پیکربندیها حفظ میشوند",
"contextMenu_description":"به شما اجازه میدهد که آیتمهای نمایش داده شده در فهرستی که وقتی روی یک آیتم کلیک راست میکنید پدیدار میشود، را پنهان کنید. آیتمهایی که منتخب نیستند پنهان میشوند",
"success":"$t(entity.playlist, {\"count\": 1}) با موفقیت بروزرسانی شد",
"publicJellyfinNote":"جلیفین به دلیلی اینکه فهرست پخش عمومیست یا خصوصی را فاش نمیکند. اگر میخواهید این عمومی باقی بماند، لطفاٌ ورودی پیشرو را منتخب داشته باشید"
"noStableReleaseToCompare":"비교할 수 있는 안정화 릴리스가 없습니다"
},
"favorites":{
"title":"$t(entity.favorite, {\"count\": 2})"
},
"windowBar":{
"paused":"(일시 정지됨) ",
"privateMode":"(비공개 모드)"
},
"folderList":{
"title":"$t(entity.folder, {\"count\": 2})"
},
"playlistList":{
"title":"$t(entity.playlist, {\"count\": 2})"
},
"collections":{
"overrideExisting":"기존 항목 덮어쓰기",
"saveAsCollection":"컬렉션으로 저장"
}
},
"table":{
@@ -458,8 +629,8 @@
"playSimilarSongs":"비슷한 곡 재생",
"previous":"이전",
"queue_clear":"재생 대기열 지우기",
"queue_moveToBottom":"선택한 곡을 가장 위로 이동",
"queue_moveToTop":"선택한 곡을 가장 아래로 이동",
"queue_moveToBottom":"선택한 곡을 가장 아래로 이동",
"queue_moveToTop":"선택한 곡을 가장 위로 이동",
"queue_remove":"선택한 항목 삭제",
"repeat":"반복",
"repeat_all":"모두 반복하기",
@@ -473,7 +644,25 @@
"toggleFullscreenPlayer":"전체화면으로 전환",
"unfavorite":"즐겨찾기 취소",
"pause":"멈춤",
"viewQueue":"대기열 보기"
"viewQueue":"대기열 보기",
"addLastShuffled":"마지막 (섞인)",
"addNextShuffled":"다음 (무작위)",
"albumRadio":"앨범 라디오",
"artistRadio":"아티스트 라디오",
"holdToShuffle":"길게 눌러 섞기",
"lyrics":"가사",
"restoreQueueFromServer":"서버에서 큐 복원",
"saveQueueToServer":"대기열을 서버에 저장",
"trackRadio":"라디오 추적",
"sleepTimer":"취침 타이머",
"sleepTimer_endOfSong":"현재 곡 종료",
"sleepTimer_endOfAlbum":"현재 앨범의 끝",
"sleepTimer_minutes":"{{count}}분",
"sleepTimer_hours":"{{count}}시간",
"sleepTimer_off":"끄다",
"sleepTimer_timeRemaining":"{{time}} 남음",
"sleepTimer_setCustom":"타이머 설정",
"sleepTimer_cancel":"타이머 취소"
},
"setting":{
"accentColor_description":"앱의 강조색상 설정",
@@ -482,7 +671,7 @@
"albumBackground":"앨범 배경이미지",
"albumBackgroundBlur_description":"앨범 배경이미지의 흐려짐 정도 조정",
"albumBackgroundBlur":"앨범배경이미지 흐려짐 크기",
"applicationHotkeys_description":"앱의 단축키 설정. 앱 전체에 적용되는 단축키를 설정하기 위해서는 체크박스에 체크하세요(PC만 가능)",
"applicationHotkeys_description":"애플리케이션 단축키를 설정합니다. 체크박스를 전환하여 전역 단축키로 설정하세요(데스크톱 전용)",
"applicationHotkeys":"앱 단축키",
"artistBackground":"아티스트 배경이미지",
"artistBackground_description":"아티스트 페이지에 아티스트가 포함된 배경이미지를 추가",
@@ -492,7 +681,7 @@
"artistConfiguration_description":"앨범아티스트 페이지에 표시할 정보 및 순서 설정",
"audioDevice_description":"음악재생에 사용할 장치 선택(웹플레이어만 가능)",
"audioDevice":"오디오 장치",
"audioExclusiveMode_description":"단독재생모드 켜기. 이 모드에서는 일반적으로 시스템의 재생장치가 고정되며 MPV로만 오디오가 재생됩니다",
"audioExclusiveMode_description":"독점 출력 모드를 활성화합니다. 이 모드에서는 일반적으로 시스템의 오디오 출력이 차단되며, 오직 mpv만이 오디오를 출력할 수 있습니다. 이 모드가 활성화된 동안에는 비주얼라이저의 시스템 오디오 캡처 기능이 작동하지 않습니다",
"audioDeviceFetchError":"Det oppstod ein feil under forsøk på å hente lydeiningar",
"authenticationFailed":"Autentisering feila",
"badAlbum":"Du ser denne sida fordi denne songen ikkje er ein del av eit album. Mest sannsynleg ser du dette fordi du har songen på toppen av musikkmappa di. Jellyfin grupperar berre spor viss dei er i ei mappe",
"badValue":"Ugyldig val \"{{value}}\". Denne verdien finst ikkje lenger",
"credentialsRequired":"Legitimasjon krevjast",
"endpointNotImplementedError":"Endepunktet {{endpoint}} er ikkje implementert for {{serverType}}",
"genericError":"Ein feil skjedde",
"invalidJson":"Ugyldig JSON",
"invalidServer":"Ugyldig sørvar",
"localFontAccessDenied":"Ingen tilgang til lokale skrifttypar",
"loginRateError":"For mange innloggingsforsøk, venlegast prøv på nytt om nokon sekundar",
"mpvRequired":"MPV nødvendig",
"multipleServerSaveQueueError":"Spelekøen har ein eller fleire songar som ikkje er frå den noverande sørvaren. Dette er ikkje støtta",
"networkError":"Ein nettverksfeil skjedde",
"noNetwork":"Sørvar utilgjengeleg",
"noNetworkDescription":"Kunne ikkje kople til denne sørvaren"
"apiRouteError":"Det går inte att dirigera begäran",
"genericError":"Ett fel uppstod",
"credentialsRequired":"Autentiseringsuppgifter som krävs",
"sessionExpiredError":"Din session har löpt ut",
"remoteEnableError":"Ett fel uppstod vid försök att $t(common.enable) servern",
"localFontAccessDenied":"åtkomst nekad till lokala teckensnitt",
"serverNotSelectedError":"ingen server vald",
"remoteDisableError":"ett fel uppstod vid försök av $t(common.disable) servern",
"localFontAccessDenied":"Åtkomst nekad till lokala teckensnitt",
"serverNotSelectedError":"Ingen server vald",
"remoteDisableError":"Ett fel uppstod vid försök av $t(common.disable) servern",
"mpvRequired":"MPV krävs",
"audioDeviceFetchError":"ett fel uppstod vid hämtning av ljudenheter",
"invalidServer":"ogiltig server",
"loginRateError":"för många inloggningsförsök, försök igen om några sekunder",
"badAlbum":"du ser denna sidan eftersom denna låten inte är en del av ett album. du ser troligtvis detta problemet för att du har en låt på toppnivån i din musikmapp. Jellyfin grupperar bara låtar om de finns i en mapp",
"badValue":"felaktigt alternativ \"{{value}}\". detta värde existerar inte längre",
"multipleServerSaveQueueError":"spelningskön har en eller flera låtar som inte är från den nuvarande valda servern. detta är inte stöttat",
"networkError":"en nätverksfel uppstod",
"notificationDenied":"åtkomst till notifieringarna var nekad. inställningen har ingen verkan",
"openError":"kunde inte öppna filen",
"settingsSyncError":"diskrepans hittades mellan inställningarna för renderingsprocessen och huvudprocessen. starta om applikationen för att ändringarna ska tillämpas"
"audioDeviceFetchError":"Ett fel uppstod vid hämtning av ljudenheter",
"invalidServer":"Ogiltig server",
"loginRateError":"För många inloggningsförsök, försök igen om några sekunder",
"badAlbum":"Du ser denna sidan eftersom denna låten inte är en del av ett album. du ser troligtvis detta problemet för att du har en låt på toppnivån i din musikmapp. Jellyfin grupperar bara låtar om de finns i en mapp",
"badValue":"Felaktigt alternativ \"{{value}}\". detta värde existerar inte längre",
"multipleServerSaveQueueError":"Spelningskön har en eller flera låtar som inte är från den nuvarande valda servern. detta är inte stöttat",
"networkError":"En nätverksfel uppstod",
"notificationDenied":"Åtkomst till notifieringarna var nekad. inställningen har ingen verkan",
"openError":"Kunde inte öppna filen",
"settingsSyncError":"Diskrepans hittades mellan inställningarna för renderingsprocessen och huvudprocessen. starta om applikationen för att ändringarna ska tillämpas"
"input_preferInstantMixDescription":"använd bara instant mixning för att få liknande låtar. användbar om du har plugin för att förändra detta beteendet"
"title":"Lägg till server",
"input_username":"Användarnamn",
"input_url":"Länk",
"input_password":"Lösenord",
"input_legacyAuthentication":"Aktivera äldre autentisering",
"input_preferInstantMixDescription":"Använd bara instant mixning för att få liknande låtar. användbar om du har plugin för att förändra detta beteendet"
},
"addToPlaylist":{
"success":"lade till $t(entity.trackWithCount, {\"count\": {{message}} }) till $t(entity.playlistWithCount, {\"count\": {{numOfPlaylists}} })",
"publicJellyfinNote":"Jellyfin visar av någon anledning inte om en spellista är publik eller inte. Om du önskar att denna ska förbli publik, så får du ha följande indata markerade"
},
"largeFetchConfirmation":{
"title":"lägg till objekt till kön",
"title":"Lägg till objekt till kön",
"description":"Åtgärden kommer att lägga till alla objekt till den nuvarande filtrerade vyn"
"audioDeviceFetchError":"Сталася помилка під час спроби отримати аудіопристрої",
"authenticationFailed":"Аутентифікація не вдалася",
"badAlbum":"Ви бачите цю сторінку, тому що ця пісня не входить до альбому. найімовірніше, ця проблема виникає, якщо у верхньому рівні вашої музичної папки знаходиться пісня. Jellyfin групує треки тільки в тому випадку, якщо вони знаходяться в папці",
"badValue":"Недійсний параметр \"{{value}}\". це значення більше не існує",
"credentialsRequired":"Необхідні дані для входу",
"endpointNotImplementedError":"Кінцева точка {{endpoint}} не реалізована для {{serverType}}",
"genericError":"Сталася помилка",
"invalidServer":"Недійсний сервер",
"localFontAccessDenied":"Відмова в доступі до локальних шрифтів",
"loginRateError":"Занадто багато спроб входу, спробуйте ще раз через кілька секунд",
"mpvRequired":"Необхідний MPV",
"multipleServerSaveQueueError":"У черзі відтворення є одна або кілька пісень, які не належать до поточного сервера. це не підтримується",
"networkError":"Сталася мережева помилка",
"noNetwork":"Сервер недоступний",
"noNetworkDescription":"Не вдалося підключитися до цього сервера",
"notificationDenied":"Дозвіл на сповіщення було відхилено. це налаштування не має впливу",
"openError":"Не вдалося відкрити файл",
"playbackError":"Сталася помилка під час спроби відтворити медіафайл",
"remoteDisableError":"Сталася помилка під час спроби $t(common.disable) віддаленого сервера",
"remoteEnableError":"Сталася помилка під час спроби $t(common.enable) віддаленого сервера",
"remotePortError":"Сталася помилка під час спроби налаштувати порт віддаленого сервера",
"remotePortWarning":"Перезапустіть сервер щоб застосувати новий порт",
"saveQueueFailed":"Не вдалося зберегти чергу",
"serverNotSelectedError":"Не вибрано жодного сервера",
"serverRequired":"Потрібен сервер",
"sessionExpiredError":"Ваша сесія закінчилася",
"systemFontError":"Сталася помилка під час спроби отримати системні шрифти",
"settingsSyncError":"Виявлено розбіжності між налаштуваннями в рендерері та основним процесом. перезапустіть програму, щоб застосувати зміни",
"invalidJson":"Недійсний JSON",
"playbackPausedDueToError":"Відтворення було призупинено через помилку",
"serverLockSingleServer":"Коли сервер заблоковано можна використовувати тільки один сервер"
"input_preferInstantMix":"Віддавати перевагу миттєвому міксу",
"input_preferInstantMixDescription":"Використовувати тільки миттєвий мікс щоб отримати подібні пісні. корисно, коли у вас є плагіни, які змінюють цю поведінку",
"input_preferRemoteUrl":"Віддавати перевагу публічній URL-адресі",
"input_remoteUrl":"Публічна URL-адреса",
"input_remoteUrlPlaceholder":"Опціонально: публічна URL-адреса для зовнішніх функцій",
"input_savePassword":"Зберегти пароль",
"input_url":"URL-адреса",
"input_username":"Ім'я користувача",
"success":"Сервер додано успішно",
"title":"Додати сервер"
},
"largeFetchConfirmation":{
"title":"Додати елементи до черги",
"description":"Ця дія додасть усі елементи в поточний відфільтрований перегляд"
"publicJellyfinNote":"Jellyfin з якоїсь причини не показує, чи є плейлист публічним чи ні. Якщо ви хочете, щоб він залишався публічним, виберіть варіант нижче",
"success":"$t(entity.playlist, {\"count\": 1}) успішно оновлено",
"autosave":"Автоматично зберігати чергу відтворення",
"autosave_description":"Увімкнути автоматичне збереження черги відтворення до вашого серверу. Це можливо тільки коли використовується Navidrome/Subsonic.Також, ви не можете мати міксовану чергу відтворення.",
"autosaveCount":"Частота автоматичного збереження черги відтворення",
"autosaveCount_description":"Кількість зміни трека перед збереженням черги. 1 (мінімум) означає змінення кожного трека",
"accentColor_description":"Встановлює акцентний колір для застосунка",
"accentColor":"Акцентний колір",
"useThemeAccentColor":"Використовувати акцентний колір теми",
"useThemeAccentColor_description":"Використовувати основний колір визначений у обраній темі замість користувацького акцентного коліру",
"useThemePrimaryShade":"Використовувати основний відтінок теми",
"useThemePrimaryShade_description":"Використовувати основний відтінок, визначений у обраній темі, для основних варіантів кольорів",
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.