Compare commits

..

4 commits

Author SHA1 Message Date
21e5a4dfd2
Merge branch 'jeffvli:development' into development
Some checks failed
Test / lint (push) Has been cancelled
2025-11-27 18:37:18 +00:00
4193dd36a1 Update README.md 2025-11-27 20:36:51 +02:00
59e94318bb feat: add audio channel configuration for MPV player
- Add audio channels setting (auto/mono/stereo) to MPV properties
- Implement UI controls in settings panel and player right controls
- Enable immediate MPV property application when setting changes
- Update default playback type to LOCAL and sample rate to 48kHz
2025-11-27 20:16:06 +02:00
Kendall Garner
cad0265e3c
fix(rpc): truncate certain fields (#1263)
* fix(rpc): truncate certain fields

* off by one...

* use elipses character
2025-11-17 02:24:52 +00:00
5 changed files with 26 additions and 101 deletions

View file

@ -4,7 +4,7 @@
> **⚠️ Fork Notice**: This is a fork of the original [Feishin](https://github.com/jeffvli/feishin) project. I created this fork primarily to add folder view functionality that the original repo wasn't able to accommodate, along with some other features I found useful. While I'll try to keep this fork in sync with the original repo, that's not the primary focus. Feishin was pretty much perfect for me - I just missed the folder view feature. That said, I'll fix any obvious bugs I encounter, but for full support and the most up-to-date version, please stay with the original repository.
>
> **Note**: I primarily use Linux desktop, so only Linux and Windows 10 issues can be addressed if needed.
> **Note**: I primarily use Linux desktop, so only Linux issues can be addressed if needed.
<p align="center">
<a href="https://github.com/antebudimir/feishin/blob/main/LICENSE">
@ -43,7 +43,6 @@ Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
- [x] Scrobble playback to your server
- [x] Smart playlist editor (Navidrome)
- [x] Synchronized and unsynchronized lyrics support
- [ ] [Request a feature](https://github.com/antebudimir/feishin/issues) or [view taskboard](https://github.com/users/jeffvli/projects/5/views/1)
## Screenshots
@ -51,81 +50,10 @@ Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
## Getting Started
### Desktop (recommended)
### Linux Desktop
Download the [latest desktop client](https://github.com/antebudimir/feishin/releases). The desktop client is the recommended way to use Feishin. It supports both the MPV and web player backends, as well as includes built-in fetching for lyrics.
#### macOS Notes
If you're using a device running macOS 12 (Monterey) or higher, [check here](https://github.com/antebudimir/feishin/issues/104#issuecomment-1553914730) for instructions on how to remove the app from quarantine.
For media keys to work, you will be prompted to allow Feishin to be a Trusted Accessibility Client. After allowing, you will need to restart Feishin for the privacy settings to take effect.
#### 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.
Simply run the installer like this:
```sh
dir=/your/application/directory
curl 'https://raw.githubusercontent.com/jeffvli/feishin/refs/heads/development/install-feishin-appimage' | sh -s -- "$dir"
```
The script also has an option to add launch arguments to run Feishin in native Wayland mode. Note that this is experimental in Electron and therefore not officially supported. If you want to use it, run this instead:
```sh
dir=/your/application/directory
curl 'https://raw.githubusercontent.com/jeffvli/feishin/refs/heads/development/install-feishin-appimage' | sh -s -- "$dir" wayland-native
```
It also provides a simple uninstall routine, removing the downloaded files:
```sh
dir=/your/application/directory
curl 'https://raw.githubusercontent.com/jeffvli/feishin/refs/heads/development/install-feishin-appimage' | sh -s -- "$dir" remove
```
The entry should show up in your Application Launcher immediately. If it does not, simply log out, wait 10 seconds, and log back in. Your Desktop Environment may alternatively provide a way to reload entries.
### Web and Docker
Visit [https://feishin.vercel.app](https://feishin.vercel.app) to use the hosted web version of Feishin. The web client only supports the web player backend.
Feishin is also available as a Docker image. The images are hosted via `ghcr.io` and are available to view [here](https://github.com/jeffvli/feishin/pkgs/container/feishin). You can run the container using the following commands:
```bash
# Run the latest version
docker run --name feishin -p 9180:9180 ghcr.io/jeffvli/feishin:latest
# Build the image locally
docker build -t feishin .
docker run --name feishin -p 9180:9180 feishin
```
#### Docker Compose
To install via Docker Compose use the following snippit. This also works on Portainer.
```yaml
services:
feishin:
container_name: feishin
image: 'ghcr.io/jeffvli/feishin:latest'
environment:
- SERVER_NAME=jellyfin # pre defined server name
- SERVER_LOCK=true # When true AND name/type/url are set, only username/password can be toggled
- SERVER_TYPE=jellyfin # navidrome also works
- SERVER_URL= # http://address:port
- PUID=1000
- PGID=1000
- UMASK=002
- TZ=America/Los_Angeles
ports:
- 9180:9180
restart: unless-stopped
```
### Configuration
1. Upon startup you will be greeted with a prompt to select the path to your MPV binary. If you do not have MPV installed, you can download it [here](https://mpv.io/installation/) or install it using any package manager supported by your OS. After inputting the path, restart the app.
@ -207,10 +135,6 @@ This project is built off of [electron-vite](https://github.com/alex8088/electro
- `pnpm run lint:fix` - Lint the project and fix linting errors
- `pnpm run i18next` - Generate i18n files
## Translation
This project uses [Weblate](https://hosted.weblate.org/projects/feishin/) for translations. If you would like to contribute, please visit the link and submit a translation.
## License
[GNU General Public License v3.0 ©](https://github.com/jeffvli/feishin/blob/dev/LICENSE)

View file

@ -18,6 +18,11 @@ import { PlayerStatus } from '/@/shared/types/types';
const discordRpc = isElectron() ? window.api.discordRpc : null;
type ActivityState = [QueueSong | undefined, number, PlayerStatus];
const MAX_FIELD_LENGTH = 127;
const truncate = (field: string) =>
field.length <= MAX_FIELD_LENGTH ? field : field.substring(0, MAX_FIELD_LENGTH - 1) + '…';
export const useDiscordRpc = () => {
const discordSettings = useDiscordSettings();
const generalSettings = useGeneralSettings();
@ -66,13 +71,15 @@ export const useDiscordRpc = () => {
};
const activity: SetActivity = {
details: (song?.name && song.name.padEnd(2, ' ')) || 'Idle',
details: truncate((song?.name && song.name.padEnd(2, ' ')) || 'Idle'),
instance: false,
largeImageKey: undefined,
largeImageText: (song?.album && song.album.padEnd(2, ' ')) || 'Unknown album',
largeImageText: truncate(
(song?.album && song.album.padEnd(2, ' ')) || 'Unknown album',
),
smallImageKey: undefined,
smallImageText: sentenceCase(current[2]),
state: (artists && artists.padEnd(2, ' ')) || 'Unknown artist',
state: truncate((artists && artists.padEnd(2, ' ')) || 'Unknown artist'),
statusDisplayType: statusDisplayMap[discordSettings.displayType],
// I would love to use the actual type as opposed to hardcoding to 2,
// but manually installing the discord-types package appears to break things

View file

@ -224,22 +224,16 @@ export const RightControls = () => {
icon={
playbackSettings.mpvProperties.audioChannels === 'mono'
? 'volumeNormal'
: playbackSettings.mpvProperties.audioChannels === 'stereo'
? 'volumeMax'
: 'volumeMute'
: 'volumeMax'
}
iconProps={{
size: 'lg',
}}
onClick={(e) => {
e.stopPropagation();
const current = playbackSettings.mpvProperties.audioChannels || 'auto';
const next =
current === 'auto'
? 'mono'
: current === 'mono'
? 'stereo'
: 'auto';
const current =
playbackSettings.mpvProperties.audioChannels || 'stereo';
const next = current === 'mono' ? 'stereo' : 'mono';
setSettings({
playback: {
...playbackSettings,
@ -251,15 +245,15 @@ export const RightControls = () => {
});
// Apply to MPV immediately
mpvPlayer?.setProperties({
'audio-channels': next === 'auto' ? undefined : next,
'audio-channels': next,
});
}}
size="sm"
tooltip={{
label:
playbackType === PlaybackType.WEB
? `Audio: ${playbackSettings.mpvProperties.audioChannels || 'auto'} (MPV only)`
: `Audio: ${playbackSettings.mpvProperties.audioChannels || 'auto'}`,
? `Audio: ${playbackSettings.mpvProperties.audioChannels || 'stereo'} (MPV only)`
: `Audio: ${playbackSettings.mpvProperties.audioChannels || 'stereo'}`,
openDelay: 0,
}}
variant="subtle"

View file

@ -33,7 +33,7 @@ export const getMpvSetting = (
) => {
switch (key) {
case 'audioChannels':
return { 'audio-channels': value === 'auto' ? undefined : value };
return { 'audio-channels': value };
case 'audioExclusiveMode':
return { 'audio-exclusive': value || 'no' };
case 'audioSampleRateHz':
@ -55,7 +55,7 @@ export const getMpvSetting = (
export const getMpvProperties = (settings: SettingsState['playback']['mpvProperties']) => {
const properties: Record<string, any> = {
'audio-channels': settings.audioChannels === 'auto' ? undefined : settings.audioChannels,
'audio-channels': settings.audioChannels || 'stereo',
'audio-exclusive': settings.audioExclusiveMode || 'no',
'audio-samplerate':
settings.audioSampleRateHz === 0 ? undefined : settings.audioSampleRateHz,
@ -278,15 +278,15 @@ export const MpvSettings = () => {
control: (
<Select
data={[
{ label: 'Auto', value: 'auto' },
{ label: 'Mono', value: 'mono' },
{ label: 'Stereo', value: 'stereo' },
]}
defaultValue={settings.mpvProperties.audioChannels || 'auto'}
defaultValue={settings.mpvProperties.audioChannels || 'stereo'}
onChange={(e) => handleSetMpvProperty('audioChannels', e)}
/>
),
description: 'Select the audio channel mode. Auto lets MPV decide based on the source.',
description:
'Select the audio channel mode. Stereo is the default, mono downmixes to a single channel.',
isHidden: settings.type !== PlaybackType.LOCAL,
title: 'Audio Channels',
},

View file

@ -124,7 +124,7 @@ const TranscodingConfigSchema = z.object({
});
const MpvSettingsSchema = z.object({
audioChannels: z.enum(['auto', 'mono', 'stereo']).optional(),
audioChannels: z.enum(['mono', 'stereo']).optional(),
audioExclusiveMode: z.enum(['no', 'yes']),
audioFormat: z.enum(['float', 's16', 's32']).optional(),
audioSampleRateHz: z.number().optional(),
@ -658,7 +658,7 @@ const initialState: SettingsState = {
mediaSession: false,
mpvExtraParameters: [],
mpvProperties: {
audioChannels: 'auto',
audioChannels: 'stereo',
audioExclusiveMode: 'no',
audioFormat: undefined,
audioSampleRateHz: 48000,