fix(transcoding): honor player forced format on the WebUI transcode flow (#5613)

* feat(stream): add ClientInfo.ForceFormat for browser-aware forced format

Restricts the client to a forced transcoding format and suppresses direct
play, but only when the client declares it supports that format. Part of #5583.

* fix(transcoding): honor player forced format on getTranscodeDecision

When the WebUI player has a forced transcoding format configured and the
browser declares it can play that format, transcode to it (suppressing
direct play). Fall back to normal negotiation with a warning when the
format is unsupported. The MaxBitRate cap still applies on top. Fixes #5583.

* test(e2e): cover player forced format on getTranscodeDecision

Forced format honored when the client supports it, falls back to negotiation
otherwise, and the MaxBitRate cap still applies on top. Part of #5583.

* feat(ui): remove obsolete 'format ignored' helper text on player form

The web player now honors the forced transcoding format, so the caveat added
in #5611 no longer applies. Reverts the Transcoding field to a plain selector.
Part of #5583.
This commit is contained in:
Deluan Quintão 2026-06-14 20:52:19 -04:00 • committed by GitHub
commit 08a027dbcc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 234 additions and 86 deletions

View file

@ -187,9 +187,6 @@
"lastSeen": "Last Seen At",
"reportRealPath": "Report Real Path",
"scrobbleEnabled": "Send Scrobbles to external services"
},
"helperTexts": {
"transcodingId": "The web player ignores the transcoding format and only enforces the Max. Bit Rate limit."
}
},
"transcoding": {

View file

@ -8,7 +8,6 @@ import {
SelectInput,
ReferenceInput,
useTranslate,
useRecordContext,
} from 'react-admin'
import { Title } from '../common'
import config from '../config'
@ -20,35 +19,17 @@ const PlayerTitle = ({ record }) => {
return <Title subTitle={`${resourceName} ${record ? record.name : ''}`} />
}
export const TranscodingInput = (props) => {
const translate = useTranslate()
const record = useRecordContext(props)
const isWebPlayer = record?.client === 'NavidromeUI'
return (
<ReferenceInput
{...props}
source="transcodingId"
reference="transcoding"
sort={{ field: 'name', order: 'ASC' }}
>
<SelectInput
source="name"
resettable
helperText={
isWebPlayer
? translate('resources.player.helperTexts.transcodingId')
: undefined
}
/>
</ReferenceInput>
)
}
const PlayerEdit = (props) => (
<Edit title={<PlayerTitle />} {...props}>
<SimpleForm variant={'outlined'}>
<TextInput source="name" validate={[required()]} />
<TranscodingInput />
<ReferenceInput
source="transcodingId"
reference="transcoding"
sort={{ field: 'name', order: 'ASC' }}
>
<SelectInput source="name" resettable />
</ReferenceInput>
<SelectInput source="maxBitRate" resettable choices={BITRATE_CHOICES} />
<BooleanInput source="reportRealPath" fullWidth />
{(config.lastFMEnabled || config.listenBrainzEnabled) && (

View file

@ -1,54 +0,0 @@
import * as React from 'react'
import { render, screen, cleanup } from '@testing-library/react'
import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest'
import { useRecordContext } from 'react-admin'
import { TranscodingInput } from './PlayerEdit'
vi.mock('react-admin', async () => {
const actual = await vi.importActual('react-admin')
return {
...actual,
useRecordContext: vi.fn(),
// Mock useTranslate to return the key verbatim so assertions don't depend
// on ra-core's out-of-provider translation behavior.
useTranslate: () => (key) => key,
// Render the inputs as simple stand-ins so we can read their props.
ReferenceInput: ({ children, variant }) => (
<div data-testid="reference-input" data-variant={variant || ''}>
{children}
</div>
),
SelectInput: ({ helperText }) => (
<div data-testid="select-input" data-helpertext={helperText || ''} />
),
}
})
describe('<TranscodingInput />', () => {
beforeEach(() => {
useRecordContext.mockReset()
})
afterEach(cleanup)
it('shows helper text for the NavidromeUI player', () => {
useRecordContext.mockReturnValue({ client: 'NavidromeUI' })
render(<TranscodingInput />)
expect(screen.getByTestId('select-input').dataset.helpertext).toBe(
'resources.player.helperTexts.transcodingId',
)
})
it('shows no helper text for other clients', () => {
useRecordContext.mockReturnValue({ client: 'DSub' })
render(<TranscodingInput />)
expect(screen.getByTestId('select-input').dataset.helpertext).toBe('')
})
it('forwards the form variant injected by SimpleForm to the input', () => {
useRecordContext.mockReturnValue({ client: 'DSub' })
render(<TranscodingInput variant="outlined" />)
expect(screen.getByTestId('reference-input').dataset.variant).toBe(
'outlined',
)
})
})