mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
Merge branch 'master' into radio-browser-search/5239
This commit is contained in:
commit
b3958b1676
@ -70,8 +70,6 @@ const (
|
|||||||
PlaceholderArtistArt = "artist-placeholder.webp"
|
PlaceholderArtistArt = "artist-placeholder.webp"
|
||||||
PlaceholderAlbumArt = "album-placeholder.webp"
|
PlaceholderAlbumArt = "album-placeholder.webp"
|
||||||
PlaceholderAvatar = "logo-192x192.png"
|
PlaceholderAvatar = "logo-192x192.png"
|
||||||
UICoverArtSize = 300
|
|
||||||
UIThumbnailSize = 80
|
|
||||||
DefaultUIVolume = 100
|
DefaultUIVolume = 100
|
||||||
DefaultUISearchDebounceMs = 200
|
DefaultUISearchDebounceMs = 200
|
||||||
|
|
||||||
@ -86,6 +84,12 @@ const (
|
|||||||
Zwsp = string('\u200b')
|
Zwsp = string('\u200b')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UICoverArtSize = 600
|
||||||
|
)
|
||||||
|
|
||||||
|
var CacheWarmerImageSizes = []int{UICoverArtSize}
|
||||||
|
|
||||||
// Prometheus options
|
// Prometheus options
|
||||||
const (
|
const (
|
||||||
PrometheusDefaultPath = "/metrics"
|
PrometheusDefaultPath = "/metrics"
|
||||||
|
|||||||
@ -142,15 +142,14 @@ func (a *cacheWarmer) doCacheImage(ctx context.Context, id model.ArtworkID) erro
|
|||||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
for _, size := range []int{consts.UICoverArtSize, consts.UIThumbnailSize} {
|
for _, size := range consts.CacheWarmerImageSizes {
|
||||||
r, _, err := a.artwork.Get(ctx, id, size, true)
|
r, _, err := a.artwork.Get(ctx, id, size, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("caching id='%s', size=%d: %w", id, size, err)
|
return fmt.Errorf("caching id='%s', size=%d: %w", id, size, err)
|
||||||
}
|
}
|
||||||
defer r.Close()
|
_, err = io.Copy(io.Discard, r)
|
||||||
if _, err = io.Copy(io.Discard, r); err != nil {
|
r.Close()
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -176,13 +176,13 @@ var _ = Describe("CacheWarmer", func() {
|
|||||||
}).Should(Equal(0))
|
}).Should(Equal(0))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("pre-caches both UICoverArtSize and UIThumbnailSize", func() {
|
It("pre-caches UICoverArtSize", func() {
|
||||||
cw := NewCacheWarmer(aw, fc).(*cacheWarmer)
|
cw := NewCacheWarmer(aw, fc).(*cacheWarmer)
|
||||||
cw.PreCache(model.MustParseArtworkID("al-1"))
|
cw.PreCache(model.MustParseArtworkID("al-1"))
|
||||||
|
|
||||||
Eventually(func() []int {
|
Eventually(func() []int {
|
||||||
return aw.getCachedSizes()
|
return aw.getCachedSizes()
|
||||||
}).Should(ContainElements(consts.UICoverArtSize, consts.UIThumbnailSize))
|
}).Should(ContainElements(consts.UICoverArtSize))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -264,6 +264,6 @@ func fillCenter(src image.Image, dstW, dstH int) image.Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
xdraw.BiLinear.Scale(dst, dst.Bounds(), src, cropRect, draw.Src, nil)
|
xdraw.CatmullRom.Scale(dst, dst.Bounds(), src, cropRect, draw.Src, nil)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|||||||
@ -155,7 +155,7 @@ func resizeStaticImage(data []byte, size int, square bool) (io.Reader, int, erro
|
|||||||
dst = image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
dst = image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
||||||
dstRect = dst.Bounds()
|
dstRect = dst.Bounds()
|
||||||
}
|
}
|
||||||
xdraw.BiLinear.Scale(dst, dstRect, original, bounds, draw.Src, nil)
|
xdraw.CatmullRom.Scale(dst, dstRect, original, bounds, draw.Src, nil)
|
||||||
|
|
||||||
buf := bufPool.Get().(*bytes.Buffer)
|
buf := bufPool.Get().(*bytes.Buffer)
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
|
|||||||
@ -2,8 +2,11 @@ package subsonic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
"github.com/navidrome/navidrome/model"
|
"github.com/navidrome/navidrome/model"
|
||||||
|
"github.com/navidrome/navidrome/model/request"
|
||||||
"github.com/navidrome/navidrome/server/subsonic/responses"
|
"github.com/navidrome/navidrome/server/subsonic/responses"
|
||||||
"github.com/navidrome/navidrome/utils/req"
|
"github.com/navidrome/navidrome/utils/req"
|
||||||
)
|
)
|
||||||
@ -66,6 +69,15 @@ func (api *Router) GetInternetRadios(r *http.Request) (*responses.Subsonic, erro
|
|||||||
StreamUrl: g.StreamUrl,
|
StreamUrl: g.StreamUrl,
|
||||||
HomepageUrl: g.HomePageUrl,
|
HomepageUrl: g.HomePageUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
player, _ := request.PlayerFrom(ctx)
|
||||||
|
if strings.Contains(conf.Server.Subsonic.LegacyClients, player.Client) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Add coverArt if not legacy client
|
||||||
|
res[i].OpenSubsonicRadio = &responses.OpenSubsonicRadio{
|
||||||
|
CoverArt: g.UploadedImage,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response := newResponse()
|
response := newResponse()
|
||||||
|
|||||||
146
server/subsonic/radio_test.go
Normal file
146
server/subsonic/radio_test.go
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package subsonic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http/httptest"
|
||||||
|
|
||||||
|
"github.com/navidrome/navidrome/conf"
|
||||||
|
"github.com/navidrome/navidrome/conf/configtest"
|
||||||
|
"github.com/navidrome/navidrome/core/auth"
|
||||||
|
"github.com/navidrome/navidrome/model"
|
||||||
|
"github.com/navidrome/navidrome/model/request"
|
||||||
|
"github.com/navidrome/navidrome/tests"
|
||||||
|
. "github.com/onsi/ginkgo/v2"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Radio", func() {
|
||||||
|
var api *Router
|
||||||
|
var ds *tests.MockDataStore
|
||||||
|
var ctx context.Context
|
||||||
|
var radioRepo *tests.MockedRadioRepo
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
ds = &tests.MockDataStore{}
|
||||||
|
auth.Init(ds)
|
||||||
|
api = &Router{ds: ds}
|
||||||
|
ctx = context.Background()
|
||||||
|
radioRepo = tests.CreateMockedRadioRepo()
|
||||||
|
ds.MockedRadio = radioRepo
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("GetInternetRadios", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
radioRepo.All = model.Radios{
|
||||||
|
{ID: "rd-1", Name: "Radio 1", StreamUrl: "http://stream1.example.com", HomePageUrl: "http://home1.example.com", UploadedImage: "rd-1_cover.jpg"},
|
||||||
|
{ID: "rd-2", Name: "Radio 2", StreamUrl: "http://stream2.example.com"},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns all radios with basic fields", func() {
|
||||||
|
r := httptest.NewRequest("GET", "/rest/getInternetRadios", nil)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
response, err := api.GetInternetRadios(r)
|
||||||
|
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(response.InternetRadioStations).ToNot(BeNil())
|
||||||
|
Expect(response.InternetRadioStations.Radios).To(HaveLen(2))
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].ID).To(Equal("rd-1"))
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].Name).To(Equal("Radio 1"))
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].StreamUrl).To(Equal("http://stream1.example.com"))
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].HomepageUrl).To(Equal("http://home1.example.com"))
|
||||||
|
Expect(response.InternetRadioStations.Radios[1].ID).To(Equal("rd-2"))
|
||||||
|
Expect(response.InternetRadioStations.Radios[1].HomepageUrl).To(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with a non-legacy client", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
DeferCleanup(configtest.SetupConfig())
|
||||||
|
conf.Server.Subsonic.LegacyClients = "legacy-client"
|
||||||
|
player := model.Player{Client: "modern-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("includes coverArt from UploadedImage", func() {
|
||||||
|
r := httptest.NewRequest("GET", "/rest/getInternetRadios", nil)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
response, err := api.GetInternetRadios(r)
|
||||||
|
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(response.InternetRadioStations.Radios).To(HaveLen(2))
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].OpenSubsonicRadio).ToNot(BeNil())
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].CoverArt).To(Equal("rd-1_cover.jpg"))
|
||||||
|
Expect(response.InternetRadioStations.Radios[1].OpenSubsonicRadio).ToNot(BeNil())
|
||||||
|
Expect(response.InternetRadioStations.Radios[1].CoverArt).To(BeEmpty())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("with a legacy client", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
DeferCleanup(configtest.SetupConfig())
|
||||||
|
conf.Server.Subsonic.LegacyClients = "legacy-client"
|
||||||
|
player := model.Player{Client: "legacy-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("does not include coverArt", func() {
|
||||||
|
r := httptest.NewRequest("GET", "/rest/getInternetRadios", nil)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
response, err := api.GetInternetRadios(r)
|
||||||
|
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(response.InternetRadioStations.Radios).To(HaveLen(2))
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].OpenSubsonicRadio).To(BeNil())
|
||||||
|
Expect(response.InternetRadioStations.Radios[1].OpenSubsonicRadio).To(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when no player in context", func() {
|
||||||
|
It("does not include coverArt (empty client matches legacy list)", func() {
|
||||||
|
DeferCleanup(configtest.SetupConfig())
|
||||||
|
conf.Server.Subsonic.LegacyClients = "legacy-client"
|
||||||
|
|
||||||
|
r := httptest.NewRequest("GET", "/rest/getInternetRadios", nil)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
response, err := api.GetInternetRadios(r)
|
||||||
|
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].OpenSubsonicRadio).To(BeNil())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when legacy clients list is empty", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
DeferCleanup(configtest.SetupConfig())
|
||||||
|
conf.Server.Subsonic.LegacyClients = ""
|
||||||
|
player := model.Player{Client: "any-client"}
|
||||||
|
ctx = request.WithPlayer(ctx, player)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("includes coverArt for all clients", func() {
|
||||||
|
r := httptest.NewRequest("GET", "/rest/getInternetRadios", nil)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
response, err := api.GetInternetRadios(r)
|
||||||
|
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].OpenSubsonicRadio).ToNot(BeNil())
|
||||||
|
Expect(response.InternetRadioStations.Radios[0].CoverArt).To(Equal("rd-1_cover.jpg"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
It("returns error when repository fails", func() {
|
||||||
|
radioRepo.SetError(true)
|
||||||
|
|
||||||
|
r := httptest.NewRequest("GET", "/rest/getInternetRadios", nil)
|
||||||
|
r = r.WithContext(ctx)
|
||||||
|
|
||||||
|
_, err := api.GetInternetRadios(r)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -509,10 +509,15 @@ type InternetRadioStations struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Radio struct {
|
type Radio struct {
|
||||||
ID string `xml:"id,attr" json:"id"`
|
ID string `xml:"id,attr" json:"id"`
|
||||||
Name string `xml:"name,attr" json:"name"`
|
Name string `xml:"name,attr" json:"name"`
|
||||||
StreamUrl string `xml:"streamUrl,attr" json:"streamUrl"`
|
StreamUrl string `xml:"streamUrl,attr" json:"streamUrl"`
|
||||||
HomepageUrl string `xml:"homePageUrl,omitempty,attr" json:"homePageUrl,omitempty"`
|
HomepageUrl string `xml:"homePageUrl,omitempty,attr" json:"homePageUrl,omitempty"`
|
||||||
|
*OpenSubsonicRadio `xml:",omitempty" json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenSubsonicRadio struct {
|
||||||
|
CoverArt string `xml:"coverArt,attr,omitempty" json:"coverArt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JukeboxStatus struct {
|
type JukeboxStatus struct {
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
useTranslate,
|
useTranslate,
|
||||||
} from 'react-admin'
|
} from 'react-admin'
|
||||||
import Lightbox from 'react-image-lightbox'
|
import Lightbox from 'react-image-lightbox'
|
||||||
|
import { COVER_ART_SIZE } from '../consts'
|
||||||
import 'react-image-lightbox/style.css'
|
import 'react-image-lightbox/style.css'
|
||||||
import subsonic from '../subsonic'
|
import subsonic from '../subsonic'
|
||||||
import {
|
import {
|
||||||
@ -254,7 +255,7 @@ const AlbumDetails = (props) => {
|
|||||||
})
|
})
|
||||||
}, [record])
|
}, [record])
|
||||||
|
|
||||||
const imageUrl = subsonic.getCoverArtUrl(record, 300)
|
const imageUrl = subsonic.getCoverArtUrl(record, COVER_ART_SIZE)
|
||||||
const fullImageUrl = subsonic.getCoverArtUrl(record)
|
const fullImageUrl = subsonic.getCoverArtUrl(record)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import {
|
|||||||
ArtistLinkField,
|
ArtistLinkField,
|
||||||
OverflowTooltip,
|
OverflowTooltip,
|
||||||
} from '../common'
|
} from '../common'
|
||||||
import { DraggableTypes } from '../consts'
|
import { COVER_ART_SIZE, DraggableTypes } from '../consts'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { AlbumDatesField } from './AlbumDatesField.jsx'
|
import { AlbumDatesField } from './AlbumDatesField.jsx'
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ const Cover = withContentRect('bounds')(({
|
|||||||
<div ref={dragAlbumRef}>
|
<div ref={dragAlbumRef}>
|
||||||
<img
|
<img
|
||||||
key={record.id} // Force re-render when record changes
|
key={record.id} // Force re-render when record changes
|
||||||
src={subsonic.getCoverArtUrl(record, 300, true)}
|
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE, true)}
|
||||||
alt={record.name}
|
alt={record.name}
|
||||||
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
||||||
onLoad={handleImageLoad}
|
onLoad={handleImageLoad}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
import Lightbox from 'react-image-lightbox'
|
import Lightbox from 'react-image-lightbox'
|
||||||
import ExpandInfoDialog from '../dialogs/ExpandInfoDialog'
|
import ExpandInfoDialog from '../dialogs/ExpandInfoDialog'
|
||||||
import AlbumInfo from '../album/AlbumInfo'
|
import AlbumInfo from '../album/AlbumInfo'
|
||||||
|
import { COVER_ART_SIZE } from '../consts'
|
||||||
import subsonic from '../subsonic'
|
import subsonic from '../subsonic'
|
||||||
import { SafeHTML } from '../common/SafeHTML'
|
import { SafeHTML } from '../common/SafeHTML'
|
||||||
|
|
||||||
@ -109,7 +110,7 @@ const DesktopArtistDetails = ({ artistInfo, record, biography }) => {
|
|||||||
<CardMedia
|
<CardMedia
|
||||||
key={record.id}
|
key={record.id}
|
||||||
component="img"
|
component="img"
|
||||||
src={subsonic.getCoverArtUrl(record, 300)}
|
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE)}
|
||||||
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
||||||
onClick={handleOpenLightbox}
|
onClick={handleOpenLightbox}
|
||||||
onLoad={handleImageLoad}
|
onLoad={handleImageLoad}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
useImageLoadingState,
|
useImageLoadingState,
|
||||||
} from '../common'
|
} from '../common'
|
||||||
import Lightbox from 'react-image-lightbox'
|
import Lightbox from 'react-image-lightbox'
|
||||||
|
import { COVER_ART_SIZE } from '../consts'
|
||||||
import subsonic from '../subsonic'
|
import subsonic from '../subsonic'
|
||||||
import { SafeHTML } from '../common/SafeHTML'
|
import { SafeHTML } from '../common/SafeHTML'
|
||||||
|
|
||||||
@ -112,7 +113,7 @@ const MobileArtistDetails = ({ artistInfo, biography, record }) => {
|
|||||||
<CardMedia
|
<CardMedia
|
||||||
key={record.id}
|
key={record.id}
|
||||||
component="img"
|
component="img"
|
||||||
src={subsonic.getCoverArtUrl(record, 300)}
|
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE)}
|
||||||
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
||||||
onClick={handleOpenLightbox}
|
onClick={handleOpenLightbox}
|
||||||
onLoad={handleImageLoad}
|
onLoad={handleImageLoad}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { useRecordContext } from 'react-admin'
|
|||||||
import { Avatar } from '@material-ui/core'
|
import { Avatar } from '@material-ui/core'
|
||||||
import { makeStyles } from '@material-ui/core/styles'
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
|
import { COVER_ART_SIZE } from '../consts'
|
||||||
import subsonic from '../subsonic'
|
import subsonic from '../subsonic'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
@ -25,7 +26,7 @@ export const CoverArtAvatar = ({
|
|||||||
const square = variant !== 'circular'
|
const square = variant !== 'circular'
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
src={subsonic.getCoverArtUrl(record, 80, square)}
|
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE, square)}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
className={clsx(classes.avatar, square && classes.square)}
|
className={clsx(classes.avatar, square && classes.square)}
|
||||||
alt={record.name}
|
alt={record.name}
|
||||||
|
|||||||
@ -26,6 +26,8 @@ DraggableTypes.ALL.push(
|
|||||||
|
|
||||||
export const RADIO_PLACEHOLDER_IMAGE = 'internet-radio-icon.svg'
|
export const RADIO_PLACEHOLDER_IMAGE = 'internet-radio-icon.svg'
|
||||||
|
|
||||||
|
export const COVER_ART_SIZE = 600
|
||||||
|
|
||||||
export const DEFAULT_SHARE_BITRATE = 128
|
export const DEFAULT_SHARE_BITRATE = 128
|
||||||
|
|
||||||
export const BITRATE_CHOICES = [
|
export const BITRATE_CHOICES = [
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
OverflowTooltip,
|
OverflowTooltip,
|
||||||
useImageLoadingState,
|
useImageLoadingState,
|
||||||
} from '../common'
|
} from '../common'
|
||||||
|
import { COVER_ART_SIZE } from '../consts'
|
||||||
import subsonic from '../subsonic'
|
import subsonic from '../subsonic'
|
||||||
|
|
||||||
const useStyles = makeStyles(
|
const useStyles = makeStyles(
|
||||||
@ -106,7 +107,7 @@ const PlaylistDetails = (props) => {
|
|||||||
handleCloseLightbox,
|
handleCloseLightbox,
|
||||||
} = useImageLoadingState(record.id)
|
} = useImageLoadingState(record.id)
|
||||||
|
|
||||||
const imageUrl = subsonic.getCoverArtUrl(record, 300, true)
|
const imageUrl = subsonic.getCoverArtUrl(record, COVER_ART_SIZE, true)
|
||||||
const fullImageUrl = subsonic.getCoverArtUrl(record)
|
const fullImageUrl = subsonic.getCoverArtUrl(record)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { makeStyles } from '@material-ui/core/styles'
|
|||||||
import { urlValidate } from '../utils/validations'
|
import { urlValidate } from '../utils/validations'
|
||||||
import { Title, ImageUploadOverlay, useImageLoadingState } from '../common'
|
import { Title, ImageUploadOverlay, useImageLoadingState } from '../common'
|
||||||
import subsonic from '../subsonic'
|
import subsonic from '../subsonic'
|
||||||
import { RADIO_PLACEHOLDER_IMAGE } from '../consts'
|
import { COVER_ART_SIZE, RADIO_PLACEHOLDER_IMAGE } from '../consts'
|
||||||
|
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
coverParent: {
|
coverParent: {
|
||||||
@ -83,7 +83,7 @@ const RadioCoverArt = ({ record }) => {
|
|||||||
{record.uploadedImage ? (
|
{record.uploadedImage ? (
|
||||||
<CardMedia
|
<CardMedia
|
||||||
component="img"
|
component="img"
|
||||||
src={subsonic.getCoverArtUrl(record, 300, true)}
|
src={subsonic.getCoverArtUrl(record, COVER_ART_SIZE, true)}
|
||||||
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
className={`${classes.cover} ${imageLoading ? classes.coverLoading : ''}`}
|
||||||
onLoad={handleImageLoad}
|
onLoad={handleImageLoad}
|
||||||
onError={handleImageError}
|
onError={handleImageError}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import subsonic from '../subsonic'
|
import subsonic from '../subsonic'
|
||||||
import { RADIO_PLACEHOLDER_IMAGE } from '../consts'
|
import { COVER_ART_SIZE, RADIO_PLACEHOLDER_IMAGE } from '../consts'
|
||||||
|
|
||||||
export async function songFromRadio(radio) {
|
export async function songFromRadio(radio) {
|
||||||
if (!radio) {
|
if (!radio) {
|
||||||
@ -8,7 +8,7 @@ export async function songFromRadio(radio) {
|
|||||||
|
|
||||||
let cover = RADIO_PLACEHOLDER_IMAGE
|
let cover = RADIO_PLACEHOLDER_IMAGE
|
||||||
if (radio.uploadedImage) {
|
if (radio.uploadedImage) {
|
||||||
cover = subsonic.getCoverArtUrl(radio, 300, true)
|
cover = subsonic.getCoverArtUrl(radio, COVER_ART_SIZE, true)
|
||||||
} else {
|
} else {
|
||||||
// Try favicon as fallback
|
// Try favicon as fallback
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { vi } from 'vitest'
|
import { vi } from 'vitest'
|
||||||
|
import { COVER_ART_SIZE } from '../consts'
|
||||||
import subsonic from './index'
|
import subsonic from './index'
|
||||||
|
|
||||||
describe('getCoverArtUrl', () => {
|
describe('getCoverArtUrl', () => {
|
||||||
@ -30,10 +31,10 @@ describe('getCoverArtUrl', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00Z',
|
updatedAt: '2023-01-01T00:00:00Z',
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = subsonic.getCoverArtUrl(playlistRecord, 300, true)
|
const url = subsonic.getCoverArtUrl(playlistRecord, COVER_ART_SIZE, true)
|
||||||
|
|
||||||
expect(url).toContain('pl-playlist-123')
|
expect(url).toContain('pl-playlist-123')
|
||||||
expect(url).toContain('size=300')
|
expect(url).toContain('size=600')
|
||||||
expect(url).toContain('square=true')
|
expect(url).toContain('square=true')
|
||||||
expect(url).toContain('_=2023-01-01T00%3A00%3A00Z')
|
expect(url).toContain('_=2023-01-01T00%3A00%3A00Z')
|
||||||
})
|
})
|
||||||
@ -44,10 +45,10 @@ describe('getCoverArtUrl', () => {
|
|||||||
sync: true,
|
sync: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = subsonic.getCoverArtUrl(playlistRecord, 300, true)
|
const url = subsonic.getCoverArtUrl(playlistRecord, COVER_ART_SIZE, true)
|
||||||
|
|
||||||
expect(url).toContain('pl-playlist-123')
|
expect(url).toContain('pl-playlist-123')
|
||||||
expect(url).toContain('size=300')
|
expect(url).toContain('size=600')
|
||||||
expect(url).toContain('square=true')
|
expect(url).toContain('square=true')
|
||||||
expect(url).not.toContain('_=')
|
expect(url).not.toContain('_=')
|
||||||
})
|
})
|
||||||
@ -59,10 +60,10 @@ describe('getCoverArtUrl', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00Z',
|
updatedAt: '2023-01-01T00:00:00Z',
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = subsonic.getCoverArtUrl(albumRecord, 300, true)
|
const url = subsonic.getCoverArtUrl(albumRecord, COVER_ART_SIZE, true)
|
||||||
|
|
||||||
expect(url).toContain('al-album-123')
|
expect(url).toContain('al-album-123')
|
||||||
expect(url).toContain('size=300')
|
expect(url).toContain('size=600')
|
||||||
expect(url).toContain('square=true')
|
expect(url).toContain('square=true')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -73,10 +74,10 @@ describe('getCoverArtUrl', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00Z',
|
updatedAt: '2023-01-01T00:00:00Z',
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = subsonic.getCoverArtUrl(songRecord, 300, true)
|
const url = subsonic.getCoverArtUrl(songRecord, COVER_ART_SIZE, true)
|
||||||
|
|
||||||
expect(url).toContain('mf-song-123')
|
expect(url).toContain('mf-song-123')
|
||||||
expect(url).toContain('size=300')
|
expect(url).toContain('size=600')
|
||||||
expect(url).toContain('square=true')
|
expect(url).toContain('square=true')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -86,10 +87,10 @@ describe('getCoverArtUrl', () => {
|
|||||||
updatedAt: '2023-01-01T00:00:00Z',
|
updatedAt: '2023-01-01T00:00:00Z',
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = subsonic.getCoverArtUrl(artistRecord, 300, true)
|
const url = subsonic.getCoverArtUrl(artistRecord, COVER_ART_SIZE, true)
|
||||||
|
|
||||||
expect(url).toContain('ar-artist-123')
|
expect(url).toContain('ar-artist-123')
|
||||||
expect(url).toContain('size=300')
|
expect(url).toContain('size=600')
|
||||||
expect(url).toContain('square=true')
|
expect(url).toContain('square=true')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user