mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-18 07:30:29 +00:00
fix(ui): read live radio title from redux in player title
The react-jinke-music-player library renders the title from its own internal snapshot of the queue item, copied when playback starts, so radioNowPlaying updates applied to the redux queue never became visible. AudioTitle now subscribes to state.player.current.radioTitle directly, guarded by isRadio and a trackId match, falling back to the previous behaviour when no live title exists. Fixes the visible half of #6; verified against real ICY stations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: hiyukoim <11803292+hiyukoim@users.noreply.github.com>
This commit is contained in:
parent
b0675e0761
commit
733fab0fcb
@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import { useMediaQuery } from '@material-ui/core'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { useSelector } from 'react-redux'
|
||||
import clsx from 'clsx'
|
||||
import { QualityInfo } from '../common'
|
||||
import { decisionService } from '../transcode'
|
||||
@ -23,6 +24,16 @@ const AudioTitle = React.memo(({ audioInfo, gainInfo, isMobile }) => {
|
||||
[song],
|
||||
)
|
||||
|
||||
// The react-jinke-music-player library keeps its own internal snapshot of
|
||||
// the queue item (audioInfo), copied when playback starts. Redux updates
|
||||
// to state.player.current never reach that snapshot, so we read the live
|
||||
// radio title from redux directly rather than relying on audioInfo alone.
|
||||
const liveRadioTitle = useSelector((state) =>
|
||||
audioInfo.isRadio && state.player?.current?.trackId === audioInfo.trackId
|
||||
? state.player.current.radioTitle
|
||||
: undefined,
|
||||
)
|
||||
|
||||
if (!song) {
|
||||
return ''
|
||||
}
|
||||
@ -45,7 +56,8 @@ const AudioTitle = React.memo(({ audioInfo, gainInfo, isMobile }) => {
|
||||
: {}
|
||||
|
||||
const subtitle = song.tags?.['subtitle']
|
||||
const displayTitle = audioInfo.radioTitle || song.radioTitle || song.title
|
||||
const displayTitle =
|
||||
liveRadioTitle || audioInfo.radioTitle || song.radioTitle || song.title
|
||||
const title = displayTitle + (subtitle ? ` (${subtitle})` : '')
|
||||
|
||||
const linkTo = audioInfo.isRadio
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React from 'react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { Provider } from 'react-redux'
|
||||
import { createStore } from 'redux'
|
||||
import AudioTitle from './AudioTitle'
|
||||
|
||||
vi.mock('@material-ui/core', async () => {
|
||||
@ -24,6 +26,11 @@ vi.mock('react-dnd', () => ({
|
||||
useDrag: vi.fn(() => [null, () => {}]),
|
||||
}))
|
||||
|
||||
const renderWithStore = (ui, playerState = {}) => {
|
||||
const store = createStore(() => ({ player: playerState }))
|
||||
return render(<Provider store={store}>{ui}</Provider>)
|
||||
}
|
||||
|
||||
describe('<AudioTitle />', () => {
|
||||
const baseSong = {
|
||||
id: 'song-1',
|
||||
@ -41,7 +48,9 @@ describe('<AudioTitle />', () => {
|
||||
|
||||
it('links to playlist when playlistId is provided', () => {
|
||||
const audioInfo = { trackId: 'track-1', song: baseSong }
|
||||
render(<AudioTitle audioInfo={audioInfo} gainInfo={{}} isMobile={false} />)
|
||||
renderWithStore(
|
||||
<AudioTitle audioInfo={audioInfo} gainInfo={{}} isMobile={false} />,
|
||||
)
|
||||
const link = screen.getByRole('link')
|
||||
expect(link.getAttribute('href')).toBe('/playlist/playlist-1/show')
|
||||
})
|
||||
@ -51,7 +60,9 @@ describe('<AudioTitle />', () => {
|
||||
trackId: 'track-1',
|
||||
song: { ...baseSong, playlistId: undefined },
|
||||
}
|
||||
render(<AudioTitle audioInfo={audioInfo} gainInfo={{}} isMobile={false} />)
|
||||
renderWithStore(
|
||||
<AudioTitle audioInfo={audioInfo} gainInfo={{}} isMobile={false} />,
|
||||
)
|
||||
const link = screen.getByRole('link')
|
||||
expect(link.getAttribute('href')).toBe('/album/album-1/show')
|
||||
})
|
||||
@ -68,11 +79,72 @@ describe('<AudioTitle />', () => {
|
||||
album: 'https://stream.example.test/radio',
|
||||
},
|
||||
}
|
||||
render(<AudioTitle audioInfo={audioInfo} gainInfo={{}} isMobile={false} />)
|
||||
renderWithStore(
|
||||
<AudioTitle audioInfo={audioInfo} gainInfo={{}} isMobile={false} />,
|
||||
)
|
||||
|
||||
expect(screen.getByText('Artist - Title')).toBeInTheDocument()
|
||||
expect(screen.getByRole('link').getAttribute('href')).toBe(
|
||||
'/radio/rd-1/show',
|
||||
)
|
||||
})
|
||||
|
||||
it('prefers the live redux title over the stale player-library snapshot', () => {
|
||||
const audioInfo = {
|
||||
trackId: 'radio-1',
|
||||
isRadio: true,
|
||||
// Note: no radioTitle here — this simulates react-jinke-music-player's
|
||||
// stale internal snapshot, which never receives redux updates.
|
||||
song: {
|
||||
id: 'radio-1',
|
||||
title: 'Station Name',
|
||||
artist: 'Station Name',
|
||||
album: 'https://stream.example.test/radio',
|
||||
},
|
||||
}
|
||||
renderWithStore(
|
||||
<AudioTitle audioInfo={audioInfo} gainInfo={{}} isMobile={false} />,
|
||||
{
|
||||
current: {
|
||||
isRadio: true,
|
||||
trackId: 'radio-1',
|
||||
radioTitle: 'Live Song - Artist',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
expect(screen.getByText('Live Song - Artist')).toBeInTheDocument()
|
||||
expect(screen.getByRole('link').getAttribute('href')).toBe(
|
||||
'/radio/radio-1/show',
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to the station name when redux current is a different radio', () => {
|
||||
const audioInfo = {
|
||||
trackId: 'radio-1',
|
||||
isRadio: true,
|
||||
song: {
|
||||
id: 'radio-1',
|
||||
title: 'Station Name',
|
||||
// Artist deliberately differs from title so getByText stays unambiguous
|
||||
artist: 'Station Artist',
|
||||
album: 'https://stream.example.test/radio',
|
||||
},
|
||||
}
|
||||
renderWithStore(
|
||||
<AudioTitle audioInfo={audioInfo} gainInfo={{}} isMobile={false} />,
|
||||
{
|
||||
current: {
|
||||
isRadio: true,
|
||||
trackId: 'radio-2',
|
||||
radioTitle: 'Some Other Live Title',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
expect(screen.getByText('Station Name')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText('Some Other Live Title'),
|
||||
).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user