Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions packages/common/src/hooks/useImageSize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,14 @@ export const useImageSize = <
preloadImageFn?: (url: string) => Promise<void>
}) => {
const [imageUrl, setImageUrl] = useState<Maybe<string>>(undefined)
const [failedUrls, setFailedUrls] = useState<Set<string>>(new Set())

const fetchWithFallback = useCallback(
async (url: string) => {
const mirrors = [...(artwork?.mirrors ?? [])]
let currentUrl = url

while (mirrors.length > 0) {
while (mirrors.length >= 0) {
try {
await preloadImageFn?.(currentUrl)
return currentUrl
Expand All @@ -81,6 +82,25 @@ export const useImageSize = <
[artwork?.mirrors, preloadImageFn]
)

const getNextMirrorUrl = useCallback(
(originalUrl: string, failedUrls: Set<string>) => {
const mirrors = artwork?.mirrors ?? []
if (mirrors.length === 0) return null

for (const mirror of mirrors) {
const nextUrl = new URL(originalUrl)
nextUrl.hostname = new URL(mirror).hostname
const mirrorUrl = nextUrl.toString()

if (!failedUrls.has(mirrorUrl)) {
return mirrorUrl
}
}
return null
},
[artwork?.mirrors]
)

const resolveImageUrl = useCallback(async () => {
if (!artwork) {
return
Expand All @@ -91,6 +111,15 @@ export const useImageSize = <
return
}

// If the original URL failed, try mirrors
if (failedUrls.has(targetUrl)) {
const mirrorUrl = getNextMirrorUrl(targetUrl, failedUrls)
if (mirrorUrl) {
setImageUrl(mirrorUrl)
return
}
}

if (IMAGE_CACHE.has(targetUrl)) {
setImageUrl(targetUrl)
return
Expand Down Expand Up @@ -136,11 +165,25 @@ export const useImageSize = <
} catch (e) {
console.error(`Unable to load image ${targetUrl} after retries: ${e}`)
}
}, [artwork, targetSize, fetchWithFallback, defaultImage])
}, [
artwork,
targetSize,
fetchWithFallback,
defaultImage,
failedUrls,
getNextMirrorUrl
])

const onError = useCallback(
(url: string) => {
setFailedUrls((prev) => new Set(prev).add(url))
},
[setFailedUrls]
)

useEffect(() => {
resolveImageUrl()
}, [resolveImageUrl])

return imageUrl
return { imageUrl, onError }
}
4 changes: 2 additions & 2 deletions packages/harmony/src/hooks/useHoverDelay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ export const useHoverDelay = (
triggeredBy === 'click'
? isClicked
: triggeredBy === 'both'
? isHovered || isClicked
: isHovered
? isHovered || isClicked
: isHovered

return {
isHovered,
Expand Down
2 changes: 1 addition & 1 deletion packages/mobile/src/components/audio/GoogleCast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const useChromecast = () => {
const previousCastState = usePrevious(castState)

const [internalCounter, setInternalCounter] = useState(0)
const imageUrl = useImageSize({
const { imageUrl } = useImageSize({
artwork: track?.artwork,
targetSize: SquareSizes.SIZE_1000_BY_1000
})
Expand Down
32 changes: 24 additions & 8 deletions packages/mobile/src/components/image/CollectionImage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useCallback } from 'react'

import { useCollection } from '@audius/common/api'
import { useImageSize } from '@audius/common/hooks'
import type { SquareSizes, ID } from '@audius/common/models'
Expand Down Expand Up @@ -49,7 +51,7 @@ export const useCollectionImage = ({
const { data: artwork } = useCollection(collectionId, {
select: (collection) => collection.artwork
})
const image = useImageSize({
const { imageUrl, onError } = useImageSize({
artwork,
targetSize: size,
defaultImage: '',
Expand All @@ -58,10 +60,11 @@ export const useCollectionImage = ({
}
})

if (image === '') {
if (imageUrl === '') {
return {
source: imageEmpty,
isFallbackImage: true
isFallbackImage: true,
onError
}
}

Expand All @@ -73,13 +76,15 @@ export const useCollectionImage = ({
return {
// @ts-ignore
source: primitiveToImageSource(artwork.url),
isFallbackImage: false
isFallbackImage: false,
onError
}
}

return {
source: primitiveToImageSource(image),
isFallbackImage: false
source: primitiveToImageSource(imageUrl),
isFallbackImage: false,
onError
}
}

Expand All @@ -98,10 +103,20 @@ export const CollectionImage = (props: CollectionImageProps) => {
const collectionImageSource = useCollectionImage({ collectionId, size })
const { cornerRadius } = useTheme()
const { skeleton } = useThemeColors()
const { source: loadedSource, isFallbackImage } = collectionImageSource
const {
source: loadedSource,
isFallbackImage,
onError
} = collectionImageSource

const source = loadedSource ?? localCollectionImageUri

const handleError = useCallback(() => {
if (source && typeof source === 'object' && 'uri' in source && source.uri) {
onError(source.uri)
}
}, [source, onError])

return (
<FastImage
{...other}
Expand All @@ -112,8 +127,9 @@ export const CollectionImage = (props: CollectionImageProps) => {
},
style
]}
source={source ?? { uri: '' }}
source={source}
onLoad={onLoad}
onError={handleError}
/>
)
}
23 changes: 16 additions & 7 deletions packages/mobile/src/components/image/CoverPhoto.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useCallback } from 'react'

import { useUser } from '@audius/common/api'
import { useImageSize } from '@audius/common/hooks'
import type { ID } from '@audius/common/models'
Expand Down Expand Up @@ -40,7 +42,7 @@ export const useCoverPhoto = ({
})
const { cover_photo, updatedCoverPhoto } = partialUser ?? {}
const coverPhoto = cover_photo
const image = useImageSize({
const { imageUrl, onError } = useImageSize({
artwork: coverPhoto,
targetSize: size,
defaultImage: '',
Expand All @@ -49,20 +51,21 @@ export const useCoverPhoto = ({
}
})

const isDefaultCover = image === ''
const isDefaultCover = imageUrl === ''
const shouldBlur = isDefaultCover && !isDefaultProfile

if (updatedCoverPhoto && !shouldBlur) {
return {
source: primitiveToImageSource(updatedCoverPhoto.url),
shouldBlur
shouldBlur,
onError
}
}

if (shouldBlur) {
return { source: profilePicture, shouldBlur }
return { source: profilePicture, shouldBlur, onError }
}
return { source: primitiveToImageSource(image), shouldBlur }
return { source: primitiveToImageSource(imageUrl), shouldBlur, onError }
}

type CoverPhotoProps = {
Expand All @@ -73,7 +76,7 @@ export const CoverPhoto = (props: CoverPhotoProps) => {
const { userId, ...imageProps } = props
const scrollY = useCurrentTabScrollY()

const { source, shouldBlur } = useCoverPhoto({
const { source, shouldBlur, onError } = useCoverPhoto({
userId,
size: WidthSizes.SIZE_640
})
Expand Down Expand Up @@ -104,11 +107,17 @@ export const CoverPhoto = (props: CoverPhotoProps) => {
})
}))

const handleError = useCallback(() => {
if (source && typeof source === 'object' && 'uri' in source && source.uri) {
onError(source.uri)
}
}, [source, onError])

if (!source) return null

return (
<Animated.View style={animatedStyle}>
<FastImage source={source} {...imageProps}>
<FastImage source={source} {...imageProps} onError={handleError}>
{shouldBlur || scrollY ? (
<AnimatedBlurView
blurType='light'
Expand Down
34 changes: 26 additions & 8 deletions packages/mobile/src/components/image/TrackImage.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useCallback } from 'react'

import { useTrack } from '@audius/common/api'
import { useImageSize } from '@audius/common/hooks'
import type { SquareSizes, ID } from '@audius/common/models'
Expand Down Expand Up @@ -49,7 +51,7 @@ export const useTrackImage = ({
return track.artwork
}
})
const image = useImageSize({
const { imageUrl, onError } = useImageSize({
artwork,
targetSize: size,
defaultImage: '',
Expand All @@ -58,10 +60,11 @@ export const useTrackImage = ({
}
})

if (image === '') {
if (imageUrl === '') {
return {
source: imageEmpty,
isFallbackImage: true
isFallbackImage: true,
onError
}
}

Expand All @@ -73,13 +76,15 @@ export const useTrackImage = ({
return {
// @ts-ignore
source: primitiveToImageSource(artwork.url),
isFallbackImage: false
isFallbackImage: false,
onError
}
}

return {
source: primitiveToImageSource(image),
isFallbackImage: false
source: primitiveToImageSource(imageUrl),
isFallbackImage: false,
onError
}
}

Expand All @@ -89,6 +94,7 @@ type TrackImageProps = {
style?: FastImageProps['style']
borderRadius?: CornerRadiusOptions
onLoad?: FastImageProps['onLoad']
onError?: FastImageProps['onError']
children?: React.ReactNode
}

Expand All @@ -106,10 +112,21 @@ export const TrackImage = (props: TrackImageProps) => {
const trackImageSource = useTrackImage({ trackId, size })
const { cornerRadius } = useTheme()
const { skeleton } = useThemeColors()
const { source: loadedSource, isFallbackImage } = trackImageSource
const { source: loadedSource, isFallbackImage, onError } = trackImageSource

const source = loadedSource ?? localTrackImageUri

const handleError = useCallback(() => {
if (
source &&
typeof source === 'object' &&
'uri' in source &&
typeof source.uri === 'string'
) {
onError(source.uri)
}
}, [source, onError])

return (
<FastImage
{...other}
Expand All @@ -120,7 +137,8 @@ export const TrackImage = (props: TrackImageProps) => {
},
style
]}
source={source ?? { uri: '' }}
source={source}
onError={handleError}
onLoad={onLoad}
/>
)
Expand Down
Loading