mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 10:03:33 +00:00
Compare commits
4 commits
v0.25.0-fo
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
| 21e5a4dfd2 | |||
| 4193dd36a1 | |||
| 59e94318bb | |||
|
|
cad0265e3c |
7 changed files with 101 additions and 101 deletions
119
README.md
119
README.md
|
|
@ -2,17 +2,21 @@
|
||||||
|
|
||||||
# Feishin
|
# Feishin
|
||||||
|
|
||||||
|
> **⚠️ 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 issues can be addressed if needed.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/jeffvli/feishin/blob/main/LICENSE">
|
<a href="https://github.com/antebudimir/feishin/blob/main/LICENSE">
|
||||||
<img src="https://img.shields.io/github/license/jeffvli/feishin?style=flat-square&color=brightgreen"
|
<img src="https://img.shields.io/github/license/antebudimir/feishin?style=flat-square&color=brightgreen"
|
||||||
alt="License">
|
alt="License">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/jeffvli/feishin/releases">
|
<a href="https://github.com/antebudimir/feishin/releases">
|
||||||
<img src="https://img.shields.io/github/v/release/jeffvli/feishin?style=flat-square&color=blue"
|
<img src="https://img.shields.io/github/v/release/antebudimir/feishin?style=flat-square&color=blue"
|
||||||
alt="Release">
|
alt="Release">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/jeffvli/feishin/releases">
|
<a href="https://github.com/antebudimir/feishin/releases">
|
||||||
<img src="https://img.shields.io/github/downloads/jeffvli/feishin/total?style=flat-square&color=orange"
|
<img src="https://img.shields.io/github/downloads/antebudimir/feishin/total?style=flat-square&color=orange"
|
||||||
alt="Downloads">
|
alt="Downloads">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -39,85 +43,16 @@ Rewrite of [Sonixd](https://github.com/jeffvli/sonixd).
|
||||||
- [x] Scrobble playback to your server
|
- [x] Scrobble playback to your server
|
||||||
- [x] Smart playlist editor (Navidrome)
|
- [x] Smart playlist editor (Navidrome)
|
||||||
- [x] Synchronized and unsynchronized lyrics support
|
- [x] Synchronized and unsynchronized lyrics support
|
||||||
- [ ] [Request a feature](https://github.com/jeffvli/feishin/issues) or [view taskboard](https://github.com/users/jeffvli/projects/5/views/1)
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<a href="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_full_screen_player.png"><img src="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_full_screen_player.png" width="49.5%"/></a> <a href="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_album_artist_detail.png"><img src="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_album_artist_detail.png" width="49.5%"/></a> <a href="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_album_detail.png"><img src="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_album_detail.png" width="49.5%"/></a> <a href="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_smart_playlist.png"><img src="https://raw.githubusercontent.com/jeffvli/feishin/development/media/preview_smart_playlist.png" width="49.5%"/></a>
|
<a href="https://raw.githubusercontent.com/antebudimir/feishin/development/media/preview_full_screen_player.png"><img src="https://raw.githubusercontent.com/antebudimir/feishin/development/media/preview_full_screen_player.png" width="49.5%"/></a> <a href="https://raw.githubusercontent.com/antebudimir/feishin/development/media/preview_album_artist_detail.png"><img src="https://raw.githubusercontent.com/antebudimir/feishin/development/media/preview_album_artist_detail.png" width="49.5%"/></a> <a href="https://raw.githubusercontent.com/antebudimir/feishin/development/media/preview_album_detail.png"><img src="https://raw.githubusercontent.com/antebudimir/feishin/development/media/preview_album_detail.png" width="49.5%"/></a> <a href="https://raw.githubusercontent.com/antebudimir/feishin/development/media/preview_smart_playlist.png"><img src="https://raw.githubusercontent.com/antebudimir/feishin/development/media/preview_smart_playlist.png" width="49.5%"/></a>
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
### Desktop (recommended)
|
### Linux Desktop
|
||||||
|
|
||||||
Download the [latest desktop client](https://github.com/jeffvli/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.
|
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/jeffvli/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
|
### Configuration
|
||||||
|
|
||||||
|
|
@ -126,7 +61,7 @@ services:
|
||||||
2. After restarting the app, you will be prompted to select a server. Click the `Open menu` button and select `Manage servers`. Click the `Add server` button in the popup and fill out all applicable details. You will need to enter the full URL to your server, including the protocol and port if applicable (e.g. `https://navidrome.my-server.com` or `http://192.168.0.1:4533`).
|
2. After restarting the app, you will be prompted to select a server. Click the `Open menu` button and select `Manage servers`. Click the `Add server` button in the popup and fill out all applicable details. You will need to enter the full URL to your server, including the protocol and port if applicable (e.g. `https://navidrome.my-server.com` or `http://192.168.0.1:4533`).
|
||||||
|
|
||||||
- **Navidrome** - For the best experience, select "Save password" when creating the server and configure the `SessionTimeout` setting in your Navidrome config to a larger value (e.g. 72h).
|
- **Navidrome** - For the best experience, select "Save password" when creating the server and configure the `SessionTimeout` setting in your Navidrome config to a larger value (e.g. 72h).
|
||||||
- **Linux users** - The default password store uses `libsecret`. `kwallet4/5/6` are also supported, but must be explicitly set in Settings > Window > Passwords/secret score.
|
- **Linux users** - The default password store uses `libsecret`. `kwallet4/5/6` are also supported, but must be explicitly set in Settings > Window > Passwords/secret score.
|
||||||
|
|
||||||
3. _Optional_ - If you want to host Feishin on a subpath (not `/`), then pass in the following environment variable: `PUBLIC_PATH=PATH`. For example, to host on `/feishin`, pass in `PUBLIC_PATH=/feishin`.
|
3. _Optional_ - If you want to host Feishin on a subpath (not `/`), then pass in the following environment variable: `PUBLIC_PATH=PATH`. For example, to host on `/feishin`, pass in `PUBLIC_PATH=/feishin`.
|
||||||
|
|
||||||
|
|
@ -145,16 +80,16 @@ Feishin supports any music server that implements a [Navidrome](https://www.navi
|
||||||
- [Navidrome](https://github.com/navidrome/navidrome)
|
- [Navidrome](https://github.com/navidrome/navidrome)
|
||||||
- [Jellyfin](https://github.com/jellyfin/jellyfin)
|
- [Jellyfin](https://github.com/jellyfin/jellyfin)
|
||||||
- [OpenSubsonic](https://opensubsonic.netlify.app/) compatible servers, such as...
|
- [OpenSubsonic](https://opensubsonic.netlify.app/) compatible servers, such as...
|
||||||
- [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced)
|
- [Airsonic-Advanced](https://github.com/airsonic-advanced/airsonic-advanced)
|
||||||
- [Ampache](https://ampache.org)
|
- [Ampache](https://ampache.org)
|
||||||
- [Astiga](https://asti.ga/)
|
- [Astiga](https://asti.ga/)
|
||||||
- [Funkwhale](https://www.funkwhale.audio/)
|
- [Funkwhale](https://www.funkwhale.audio/)
|
||||||
- [Gonic](https://github.com/sentriz/gonic)
|
- [Gonic](https://github.com/sentriz/gonic)
|
||||||
- [LMS](https://github.com/epoupon/lms)
|
- [LMS](https://github.com/epoupon/lms)
|
||||||
- [Nextcloud Music](https://apps.nextcloud.com/apps/music)
|
- [Nextcloud Music](https://apps.nextcloud.com/apps/music)
|
||||||
- [Supysonic](https://github.com/spl0k/supysonic)
|
- [Supysonic](https://github.com/spl0k/supysonic)
|
||||||
- [Qm-Music](https://github.com/chenqimiao/qm-music)
|
- [Qm-Music](https://github.com/chenqimiao/qm-music)
|
||||||
- More (?)
|
- More (?)
|
||||||
|
|
||||||
### I have the issue "The SUID sandbox helper binary was found, but is not configured correctly" on Linux
|
### I have the issue "The SUID sandbox helper binary was found, but is not configured correctly" on Linux
|
||||||
|
|
||||||
|
|
@ -165,7 +100,7 @@ chmod 4755 chrome-sandbox
|
||||||
sudo chown root:root chrome-sandbox
|
sudo chown root:root chrome-sandbox
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
|
@ -200,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 lint:fix` - Lint the project and fix linting errors
|
||||||
- `pnpm run i18next` - Generate i18n files
|
- `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
|
## License
|
||||||
|
|
||||||
[GNU General Public License v3.0 ©](https://github.com/jeffvli/feishin/blob/dev/LICENSE)
|
[GNU General Public License v3.0 ©](https://github.com/jeffvli/feishin/blob/dev/LICENSE)
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ linux:
|
||||||
npmRebuild: false
|
npmRebuild: false
|
||||||
publish:
|
publish:
|
||||||
provider: github
|
provider: github
|
||||||
owner: jeffvli
|
owner: antebudimir
|
||||||
repo: feishin
|
repo: feishin
|
||||||
channel: latest
|
channel: latest
|
||||||
releaseType: draft
|
releaseType: draft
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "feishin",
|
"name": "feishin",
|
||||||
"version": "0.24.0",
|
"version": "0.25.0-fork",
|
||||||
"description": "A modern self-hosted music player.",
|
"description": "A modern self-hosted music player.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"subsonic",
|
"subsonic",
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ import { PlayerStatus } from '/@/shared/types/types';
|
||||||
const discordRpc = isElectron() ? window.api.discordRpc : null;
|
const discordRpc = isElectron() ? window.api.discordRpc : null;
|
||||||
type ActivityState = [QueueSong | undefined, number, PlayerStatus];
|
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 = () => {
|
export const useDiscordRpc = () => {
|
||||||
const discordSettings = useDiscordSettings();
|
const discordSettings = useDiscordSettings();
|
||||||
const generalSettings = useGeneralSettings();
|
const generalSettings = useGeneralSettings();
|
||||||
|
|
@ -66,13 +71,15 @@ export const useDiscordRpc = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const activity: SetActivity = {
|
const activity: SetActivity = {
|
||||||
details: (song?.name && song.name.padEnd(2, ' ')) || 'Idle',
|
details: truncate((song?.name && song.name.padEnd(2, ' ')) || 'Idle'),
|
||||||
instance: false,
|
instance: false,
|
||||||
largeImageKey: undefined,
|
largeImageKey: undefined,
|
||||||
largeImageText: (song?.album && song.album.padEnd(2, ' ')) || 'Unknown album',
|
largeImageText: truncate(
|
||||||
|
(song?.album && song.album.padEnd(2, ' ')) || 'Unknown album',
|
||||||
|
),
|
||||||
smallImageKey: undefined,
|
smallImageKey: undefined,
|
||||||
smallImageText: sentenceCase(current[2]),
|
smallImageText: sentenceCase(current[2]),
|
||||||
state: (artists && artists.padEnd(2, ' ')) || 'Unknown artist',
|
state: truncate((artists && artists.padEnd(2, ' ')) || 'Unknown artist'),
|
||||||
statusDisplayType: statusDisplayMap[discordSettings.displayType],
|
statusDisplayType: statusDisplayMap[discordSettings.displayType],
|
||||||
// I would love to use the actual type as opposed to hardcoding to 2,
|
// 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
|
// but manually installing the discord-types package appears to break things
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import { PlaybackType } from '/@/shared/types/types';
|
||||||
|
|
||||||
const ipc = isElectron() ? window.api.ipc : null;
|
const ipc = isElectron() ? window.api.ipc : null;
|
||||||
const remote = isElectron() ? window.api.remote : null;
|
const remote = isElectron() ? window.api.remote : null;
|
||||||
|
const mpvPlayer = isElectron() ? window.api.mpvPlayer : null;
|
||||||
|
|
||||||
export const RightControls = () => {
|
export const RightControls = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
@ -218,6 +219,46 @@ export const RightControls = () => {
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
<Group align="center" gap="xs" wrap="nowrap">
|
<Group align="center" gap="xs" wrap="nowrap">
|
||||||
|
{(playbackType === PlaybackType.LOCAL || playbackType === PlaybackType.WEB) && (
|
||||||
|
<ActionIcon
|
||||||
|
icon={
|
||||||
|
playbackSettings.mpvProperties.audioChannels === 'mono'
|
||||||
|
? 'volumeNormal'
|
||||||
|
: 'volumeMax'
|
||||||
|
}
|
||||||
|
iconProps={{
|
||||||
|
size: 'lg',
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const current =
|
||||||
|
playbackSettings.mpvProperties.audioChannels || 'stereo';
|
||||||
|
const next = current === 'mono' ? 'stereo' : 'mono';
|
||||||
|
setSettings({
|
||||||
|
playback: {
|
||||||
|
...playbackSettings,
|
||||||
|
mpvProperties: {
|
||||||
|
...playbackSettings.mpvProperties,
|
||||||
|
audioChannels: next,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
// Apply to MPV immediately
|
||||||
|
mpvPlayer?.setProperties({
|
||||||
|
'audio-channels': next,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
tooltip={{
|
||||||
|
label:
|
||||||
|
playbackType === PlaybackType.WEB
|
||||||
|
? `Audio: ${playbackSettings.mpvProperties.audioChannels || 'stereo'} (MPV only)`
|
||||||
|
: `Audio: ${playbackSettings.mpvProperties.audioChannels || 'stereo'}`,
|
||||||
|
openDelay: 0,
|
||||||
|
}}
|
||||||
|
variant="subtle"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<DropdownMenu arrowOffset={12} offset={0} position="top-end" width={425} withArrow>
|
<DropdownMenu arrowOffset={12} offset={0} position="top-end" width={425} withArrow>
|
||||||
<DropdownMenu.Target>
|
<DropdownMenu.Target>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ export const getMpvSetting = (
|
||||||
value: any,
|
value: any,
|
||||||
) => {
|
) => {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
|
case 'audioChannels':
|
||||||
|
return { 'audio-channels': value };
|
||||||
case 'audioExclusiveMode':
|
case 'audioExclusiveMode':
|
||||||
return { 'audio-exclusive': value || 'no' };
|
return { 'audio-exclusive': value || 'no' };
|
||||||
case 'audioSampleRateHz':
|
case 'audioSampleRateHz':
|
||||||
|
|
@ -53,6 +55,7 @@ export const getMpvSetting = (
|
||||||
|
|
||||||
export const getMpvProperties = (settings: SettingsState['playback']['mpvProperties']) => {
|
export const getMpvProperties = (settings: SettingsState['playback']['mpvProperties']) => {
|
||||||
const properties: Record<string, any> = {
|
const properties: Record<string, any> = {
|
||||||
|
'audio-channels': settings.audioChannels || 'stereo',
|
||||||
'audio-exclusive': settings.audioExclusiveMode || 'no',
|
'audio-exclusive': settings.audioExclusiveMode || 'no',
|
||||||
'audio-samplerate':
|
'audio-samplerate':
|
||||||
settings.audioSampleRateHz === 0 ? undefined : settings.audioSampleRateHz,
|
settings.audioSampleRateHz === 0 ? undefined : settings.audioSampleRateHz,
|
||||||
|
|
@ -271,6 +274,22 @@ export const MpvSettings = () => {
|
||||||
isHidden: settings.type !== PlaybackType.LOCAL,
|
isHidden: settings.type !== PlaybackType.LOCAL,
|
||||||
title: t('setting.gaplessAudio', { postProcess: 'sentenceCase' }),
|
title: t('setting.gaplessAudio', { postProcess: 'sentenceCase' }),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
control: (
|
||||||
|
<Select
|
||||||
|
data={[
|
||||||
|
{ label: 'Mono', value: 'mono' },
|
||||||
|
{ label: 'Stereo', value: 'stereo' },
|
||||||
|
]}
|
||||||
|
defaultValue={settings.mpvProperties.audioChannels || 'stereo'}
|
||||||
|
onChange={(e) => handleSetMpvProperty('audioChannels', e)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
description:
|
||||||
|
'Select the audio channel mode. Stereo is the default, mono downmixes to a single channel.',
|
||||||
|
isHidden: settings.type !== PlaybackType.LOCAL,
|
||||||
|
title: 'Audio Channels',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
control: (
|
control: (
|
||||||
<NumberInput
|
<NumberInput
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ const TranscodingConfigSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
const MpvSettingsSchema = z.object({
|
const MpvSettingsSchema = z.object({
|
||||||
|
audioChannels: z.enum(['mono', 'stereo']).optional(),
|
||||||
audioExclusiveMode: z.enum(['no', 'yes']),
|
audioExclusiveMode: z.enum(['no', 'yes']),
|
||||||
audioFormat: z.enum(['float', 's16', 's32']).optional(),
|
audioFormat: z.enum(['float', 's16', 's32']).optional(),
|
||||||
audioSampleRateHz: z.number().optional(),
|
audioSampleRateHz: z.number().optional(),
|
||||||
|
|
@ -657,9 +658,10 @@ const initialState: SettingsState = {
|
||||||
mediaSession: false,
|
mediaSession: false,
|
||||||
mpvExtraParameters: [],
|
mpvExtraParameters: [],
|
||||||
mpvProperties: {
|
mpvProperties: {
|
||||||
|
audioChannels: 'stereo',
|
||||||
audioExclusiveMode: 'no',
|
audioExclusiveMode: 'no',
|
||||||
audioFormat: undefined,
|
audioFormat: undefined,
|
||||||
audioSampleRateHz: 0,
|
audioSampleRateHz: 48000,
|
||||||
gaplessAudio: 'weak',
|
gaplessAudio: 'weak',
|
||||||
replayGainClip: true,
|
replayGainClip: true,
|
||||||
replayGainFallbackDB: undefined,
|
replayGainFallbackDB: undefined,
|
||||||
|
|
@ -678,7 +680,7 @@ const initialState: SettingsState = {
|
||||||
transcode: {
|
transcode: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
type: PlaybackType.WEB,
|
type: PlaybackType.LOCAL,
|
||||||
webAudio: true,
|
webAudio: true,
|
||||||
},
|
},
|
||||||
remote: {
|
remote: {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue