mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-29 13:56:52 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6528859ea9 | |||
| 704745eb7c | |||
| 2759cfee97 | |||
| 755f0aab9d |
+6
-1
@@ -21,7 +21,12 @@ export const ItemDetailRowPlayControlCell = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.cellWrapper}>
|
<div className={styles.cellWrapper}>
|
||||||
<HoverCard openDelay={300} position="top" withArrow>
|
<HoverCard
|
||||||
|
offset={{ crossAxis: 32, mainAxis: 16 }}
|
||||||
|
openDelay={300}
|
||||||
|
position="top"
|
||||||
|
withArrow
|
||||||
|
>
|
||||||
<HoverCard.Target>
|
<HoverCard.Target>
|
||||||
<div className={styles.playTarget}>{indexContent}</div>
|
<div className={styles.playTarget}>{indexContent}</div>
|
||||||
</HoverCard.Target>
|
</HoverCard.Target>
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export const RowPlayControlCell = (
|
|||||||
<TableColumnContainer {...props} className={styles.expansionCell}>
|
<TableColumnContainer {...props} className={styles.expansionCell}>
|
||||||
<div className={styles.expansionInner}>
|
<div className={styles.expansionInner}>
|
||||||
{showPlayControls ? (
|
{showPlayControls ? (
|
||||||
<HoverCard openDelay={300} position="top" withArrow>
|
<HoverCard openDelay={300} position="top" withArrow withinPortal={false}>
|
||||||
<HoverCard.Target>{expansionTarget}</HoverCard.Target>
|
<HoverCard.Target>{expansionTarget}</HoverCard.Target>
|
||||||
<HoverCard.Dropdown onClick={(e) => e.stopPropagation()}>
|
<HoverCard.Dropdown onClick={(e) => e.stopPropagation()}>
|
||||||
<ItemRowPlayControls onPlay={onPlay} />
|
<ItemRowPlayControls onPlay={onPlay} />
|
||||||
@@ -110,7 +110,7 @@ export const RowPlayControlCell = (
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TableColumnTextContainer {...props} className={styles.fullSizeContent}>
|
<TableColumnTextContainer {...props} className={styles.fullSizeContent}>
|
||||||
<HoverCard openDelay={300} position="top" withArrow>
|
<HoverCard openDelay={300} position="top" withArrow withinPortal={false}>
|
||||||
<HoverCard.Target>
|
<HoverCard.Target>
|
||||||
<Flex className={styles.indexContent} justify="center" w="100%">
|
<Flex className={styles.indexContent} justify="center" w="100%">
|
||||||
{getIndexDisplay(false)}
|
{getIndexDisplay(false)}
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ export interface WebPlayerEngineHandle extends AudioPlayer {
|
|||||||
interface WebPlayerEngineProps {
|
interface WebPlayerEngineProps {
|
||||||
isMuted: boolean;
|
isMuted: boolean;
|
||||||
isTransitioning: boolean;
|
isTransitioning: boolean;
|
||||||
|
loopPlayer1: boolean;
|
||||||
|
loopPlayer2: boolean;
|
||||||
onEndedPlayer1: () => void;
|
onEndedPlayer1: () => void;
|
||||||
onEndedPlayer2: () => void;
|
onEndedPlayer2: () => void;
|
||||||
onErrorPause: () => void;
|
onErrorPause: () => void;
|
||||||
@@ -55,6 +57,8 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
|||||||
const {
|
const {
|
||||||
isMuted,
|
isMuted,
|
||||||
isTransitioning,
|
isTransitioning,
|
||||||
|
loopPlayer1,
|
||||||
|
loopPlayer2,
|
||||||
onEndedPlayer1,
|
onEndedPlayer1,
|
||||||
onEndedPlayer2,
|
onEndedPlayer2,
|
||||||
onErrorPause,
|
onErrorPause,
|
||||||
@@ -292,8 +296,9 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
|||||||
controls={false}
|
controls={false}
|
||||||
height={0}
|
height={0}
|
||||||
id="web-player-1"
|
id="web-player-1"
|
||||||
|
loop={loopPlayer1}
|
||||||
muted={isMuted}
|
muted={isMuted}
|
||||||
onEnded={src1 ? () => onEndedPlayer1() : undefined}
|
onEnded={src1 && !loopPlayer1 ? () => onEndedPlayer1() : undefined}
|
||||||
onError={handleOnError(
|
onError={handleOnError(
|
||||||
player1Ref,
|
player1Ref,
|
||||||
() => onEndedPlayer1(),
|
() => onEndedPlayer1(),
|
||||||
@@ -317,8 +322,9 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
|||||||
controls={false}
|
controls={false}
|
||||||
height={0}
|
height={0}
|
||||||
id="web-player-2"
|
id="web-player-2"
|
||||||
|
loop={loopPlayer2}
|
||||||
muted={isMuted}
|
muted={isMuted}
|
||||||
onEnded={src2 ? () => onEndedPlayer2() : undefined}
|
onEnded={src2 && !loopPlayer2 ? () => onEndedPlayer2() : undefined}
|
||||||
onError={handleOnError(
|
onError={handleOnError(
|
||||||
player2Ref,
|
player2Ref,
|
||||||
() => onEndedPlayer2(),
|
() => onEndedPlayer2(),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type ReactPlayer from 'react-player';
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||||
import {
|
import {
|
||||||
WebPlayerEngine,
|
WebPlayerEngine,
|
||||||
WebPlayerEngineHandle,
|
WebPlayerEngineHandle,
|
||||||
@@ -20,12 +21,13 @@ import {
|
|||||||
usePlayerData,
|
usePlayerData,
|
||||||
usePlayerMuted,
|
usePlayerMuted,
|
||||||
usePlayerProperties,
|
usePlayerProperties,
|
||||||
|
usePlayerRepeat,
|
||||||
usePlayerStoreBase,
|
usePlayerStoreBase,
|
||||||
usePlayerVolume,
|
usePlayerVolume,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { toast } from '/@/shared/components/toast/toast';
|
import { toast } from '/@/shared/components/toast/toast';
|
||||||
import { QueueSong } from '/@/shared/types/domain-types';
|
import { QueueSong } from '/@/shared/types/domain-types';
|
||||||
import { CrossfadeStyle, PlayerStatus, PlayerStyle } from '/@/shared/types/types';
|
import { CrossfadeStyle, PlayerRepeat, PlayerStatus, PlayerStyle } from '/@/shared/types/types';
|
||||||
|
|
||||||
const PLAY_PAUSE_FADE_DURATION = 300;
|
const PLAY_PAUSE_FADE_DURATION = 300;
|
||||||
const PLAY_PAUSE_FADE_INTERVAL = 10;
|
const PLAY_PAUSE_FADE_INTERVAL = 10;
|
||||||
@@ -34,6 +36,8 @@ export function WebPlayer() {
|
|||||||
const playerRef = useRef<null | WebPlayerEngineHandle>(null);
|
const playerRef = useRef<null | WebPlayerEngineHandle>(null);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { num, player1, player2, status } = usePlayerData();
|
const { num, player1, player2, status } = usePlayerData();
|
||||||
|
const repeat = usePlayerRepeat();
|
||||||
|
const repeatOneProgressRef = useRef({ player1: 0, player2: 0 });
|
||||||
const { mediaAutoNext, mediaPause, setTimestamp } = usePlayerActions();
|
const { mediaAutoNext, mediaPause, setTimestamp } = usePlayerActions();
|
||||||
const playback = useMpvSettings();
|
const playback = useMpvSettings();
|
||||||
const { webAudio } = useWebAudio();
|
const { webAudio } = useWebAudio();
|
||||||
@@ -97,12 +101,37 @@ export function WebPlayer() {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleRepeatOne = useCallback(
|
||||||
|
(playerId: 1 | 2, playedSeconds: number, duration: number) => {
|
||||||
|
if (repeat !== PlayerRepeat.ONE || duration <= 0 || num !== playerId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = playerId === 1 ? 'player1' : 'player2';
|
||||||
|
const last = repeatOneProgressRef.current[key];
|
||||||
|
repeatOneProgressRef.current[key] = playedSeconds;
|
||||||
|
|
||||||
|
if (last > duration * 0.85 && playedSeconds < duration * 0.15) {
|
||||||
|
setTimestamp(0);
|
||||||
|
eventEmitter.emit('PLAYER_REPEATED', {
|
||||||
|
index: usePlayerStoreBase.getState().player.index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[num, repeat, setTimestamp],
|
||||||
|
);
|
||||||
|
|
||||||
const onProgressPlayer1 = useCallback(
|
const onProgressPlayer1 = useCallback(
|
||||||
(e: PlayerOnProgressProps) => {
|
(e: PlayerOnProgressProps) => {
|
||||||
if (!playerRef.current?.player1()) {
|
if (!playerRef.current?.player1()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (repeat === PlayerRepeat.ONE) {
|
||||||
|
handleRepeatOne(1, e.playedSeconds, getDuration(playerRef.current.player1().ref));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (transitionType) {
|
switch (transitionType) {
|
||||||
case PlayerStyle.CROSSFADE:
|
case PlayerStyle.CROSSFADE:
|
||||||
crossfadeHandler({
|
crossfadeHandler({
|
||||||
@@ -132,7 +161,17 @@ export function WebPlayer() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[crossfadeDuration, crossfadeStyle, isTransitioning, num, player2, transitionType, volume],
|
[
|
||||||
|
crossfadeDuration,
|
||||||
|
crossfadeStyle,
|
||||||
|
handleRepeatOne,
|
||||||
|
isTransitioning,
|
||||||
|
num,
|
||||||
|
player2,
|
||||||
|
repeat,
|
||||||
|
transitionType,
|
||||||
|
volume,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onProgressPlayer2 = useCallback(
|
const onProgressPlayer2 = useCallback(
|
||||||
@@ -141,6 +180,11 @@ export function WebPlayer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (repeat === PlayerRepeat.ONE) {
|
||||||
|
handleRepeatOne(2, e.playedSeconds, getDuration(playerRef.current.player2().ref));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (transitionType) {
|
switch (transitionType) {
|
||||||
case PlayerStyle.CROSSFADE:
|
case PlayerStyle.CROSSFADE:
|
||||||
crossfadeHandler({
|
crossfadeHandler({
|
||||||
@@ -170,7 +214,17 @@ export function WebPlayer() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[crossfadeDuration, crossfadeStyle, isTransitioning, num, player1, transitionType, volume],
|
[
|
||||||
|
crossfadeDuration,
|
||||||
|
crossfadeStyle,
|
||||||
|
handleRepeatOne,
|
||||||
|
isTransitioning,
|
||||||
|
num,
|
||||||
|
player1,
|
||||||
|
repeat,
|
||||||
|
transitionType,
|
||||||
|
volume,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOnEndedPlayer1 = useCallback(() => {
|
const handleOnEndedPlayer1 = useCallback(() => {
|
||||||
@@ -474,10 +528,15 @@ export function WebPlayer() {
|
|||||||
});
|
});
|
||||||
}, [mediaPause, t]);
|
}, [mediaPause, t]);
|
||||||
|
|
||||||
|
const loopPlayer1 = repeat === PlayerRepeat.ONE && num === 1;
|
||||||
|
const loopPlayer2 = repeat === PlayerRepeat.ONE && num === 2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WebPlayerEngine
|
<WebPlayerEngine
|
||||||
isMuted={isMuted}
|
isMuted={isMuted}
|
||||||
isTransitioning={Boolean(isTransitioning)}
|
isTransitioning={Boolean(isTransitioning)}
|
||||||
|
loopPlayer1={loopPlayer1}
|
||||||
|
loopPlayer2={loopPlayer2}
|
||||||
onEndedPlayer1={handleOnEndedPlayer1}
|
onEndedPlayer1={handleOnEndedPlayer1}
|
||||||
onEndedPlayer2={handleOnEndedPlayer2}
|
onEndedPlayer2={handleOnEndedPlayer2}
|
||||||
onErrorPause={handleOnErrorPause}
|
onErrorPause={handleOnErrorPause}
|
||||||
|
|||||||
@@ -136,6 +136,8 @@ export function RadioWebPlayer() {
|
|||||||
<WebPlayerEngine
|
<WebPlayerEngine
|
||||||
isMuted={isMuted}
|
isMuted={isMuted}
|
||||||
isTransitioning={false}
|
isTransitioning={false}
|
||||||
|
loopPlayer1={false}
|
||||||
|
loopPlayer2={false}
|
||||||
onEndedPlayer1={onEndedPlayer1}
|
onEndedPlayer1={onEndedPlayer1}
|
||||||
onEndedPlayer2={() => {}}
|
onEndedPlayer2={() => {}}
|
||||||
onErrorPause={() => {}}
|
onErrorPause={() => {}}
|
||||||
|
|||||||
@@ -139,6 +139,25 @@ export function calculateNextSong(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDualPlayerSongs(
|
||||||
|
playerNum: 1 | 2,
|
||||||
|
currentSong: QueueSong | undefined,
|
||||||
|
nextSong: QueueSong | undefined,
|
||||||
|
repeat: PlayerRepeat,
|
||||||
|
): { player1: QueueSong | undefined; player2: QueueSong | undefined } {
|
||||||
|
if (repeat === PlayerRepeat.ONE) {
|
||||||
|
return {
|
||||||
|
player1: playerNum === 1 ? currentSong : undefined,
|
||||||
|
player2: playerNum === 2 ? currentSong : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
player1: playerNum === 1 ? currentSong : nextSong,
|
||||||
|
player2: playerNum === 2 ? currentSong : nextSong,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to check if shuffle is enabled
|
// Helper function to check if shuffle is enabled
|
||||||
export function isShuffleEnabled(state: {
|
export function isShuffleEnabled(state: {
|
||||||
player: { shuffle: PlayerShuffle };
|
player: { shuffle: PlayerShuffle };
|
||||||
@@ -800,13 +819,20 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
|||||||
nextSong = calculateNextSong(queueIndex, queue.items, repeat);
|
nextSong = calculateNextSong(queueIndex, queue.items, repeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { player1, player2 } = getDualPlayerSongs(
|
||||||
|
state.player.playerNum,
|
||||||
|
currentSong,
|
||||||
|
nextSong,
|
||||||
|
repeat,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentSong,
|
currentSong,
|
||||||
index: queueIndex, // Return the actual queue position for display
|
index: queueIndex, // Return the actual queue position for display
|
||||||
nextSong,
|
nextSong,
|
||||||
num: state.player.playerNum,
|
num: state.player.playerNum,
|
||||||
player1: state.player.playerNum === 1 ? currentSong : nextSong,
|
player1,
|
||||||
player2: state.player.playerNum === 2 ? currentSong : nextSong,
|
player2,
|
||||||
previousSong,
|
previousSong,
|
||||||
queueLength: state.queue.default.length,
|
queueLength: state.queue.default.length,
|
||||||
status: state.player.status,
|
status: state.player.status,
|
||||||
@@ -895,12 +921,21 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
|||||||
? stateSnapshot.queue.shuffled.length
|
? stateSnapshot.queue.shuffled.length
|
||||||
: queue.items.length;
|
: queue.items.length;
|
||||||
|
|
||||||
const newPlayerNum = player.playerNum === 1 ? 2 : 1;
|
|
||||||
const { nextIndex: nextPlaybackIndex, shouldPause } = calculateNextIndex(
|
const { nextIndex: nextPlaybackIndex, shouldPause } = calculateNextIndex(
|
||||||
currentIndex,
|
currentIndex,
|
||||||
playbackLength,
|
playbackLength,
|
||||||
repeat,
|
repeat,
|
||||||
);
|
);
|
||||||
|
const isRepeatOneSameTrack =
|
||||||
|
repeat === PlayerRepeat.ONE && nextPlaybackIndex === currentIndex;
|
||||||
|
// Dual web players alternate for gapless/crossfade between tracks. Repeat-one
|
||||||
|
// replays the same track — keep playerNum so Chromium stays bound to the same
|
||||||
|
// <audio> element and hardware media keys keep working.
|
||||||
|
const newPlayerNum = isRepeatOneSameTrack
|
||||||
|
? player.playerNum
|
||||||
|
: player.playerNum === 1
|
||||||
|
? 2
|
||||||
|
: 1;
|
||||||
const pauseOnNext = player.pauseOnNextSongEnd;
|
const pauseOnNext = player.pauseOnNextSongEnd;
|
||||||
const newStatus =
|
const newStatus =
|
||||||
shouldPause || pauseOnNext ? PlayerStatus.PAUSED : PlayerStatus.PLAYING;
|
shouldPause || pauseOnNext ? PlayerStatus.PAUSED : PlayerStatus.PLAYING;
|
||||||
@@ -963,13 +998,20 @@ export const usePlayerStoreBase = createWithEqualityFn<PlayerState>()(
|
|||||||
currentQueueIndex > 0 ? queue.items[currentQueueIndex - 1] : undefined;
|
currentQueueIndex > 0 ? queue.items[currentQueueIndex - 1] : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { player1, player2 } = getDualPlayerSongs(
|
||||||
|
newPlayerNum,
|
||||||
|
currentSong,
|
||||||
|
nextSong,
|
||||||
|
repeat,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentSong,
|
currentSong,
|
||||||
index: currentQueueIndex,
|
index: currentQueueIndex,
|
||||||
nextSong,
|
nextSong,
|
||||||
num: newPlayerNum,
|
num: newPlayerNum,
|
||||||
player1: newPlayerNum === 1 ? currentSong : nextSong,
|
player1,
|
||||||
player2: newPlayerNum === 2 ? currentSong : nextSong,
|
player2,
|
||||||
previousSong,
|
previousSong,
|
||||||
queueLength: queue.items.length,
|
queueLength: queue.items.length,
|
||||||
status: newStatus,
|
status: newStatus,
|
||||||
@@ -1962,13 +2004,20 @@ export const usePlayerData = (): PlayerData => {
|
|||||||
nextSong = calculateNextSong(queueIndex, queue.items, repeat);
|
nextSong = calculateNextSong(queueIndex, queue.items, repeat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { player1, player2 } = getDualPlayerSongs(
|
||||||
|
state.player.playerNum,
|
||||||
|
currentSong,
|
||||||
|
nextSong,
|
||||||
|
repeat,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentSong,
|
currentSong,
|
||||||
index: queueIndex, // Return the actual queue position for display
|
index: queueIndex, // Return the actual queue position for display
|
||||||
nextSong,
|
nextSong,
|
||||||
num: state.player.playerNum,
|
num: state.player.playerNum,
|
||||||
player1: state.player.playerNum === 1 ? currentSong : nextSong,
|
player1,
|
||||||
player2: state.player.playerNum === 2 ? currentSong : nextSong,
|
player2,
|
||||||
previousSong,
|
previousSong,
|
||||||
queueLength: state.queue.default.length,
|
queueLength: state.queue.default.length,
|
||||||
status: state.player.status,
|
status: state.player.status,
|
||||||
|
|||||||
@@ -89,18 +89,11 @@ export function BaseImage({
|
|||||||
const effectiveImageRequest =
|
const effectiveImageRequest =
|
||||||
isInSessionCache || !enableDebounce ? rawImageRequest : debouncedImageRequest;
|
isInSessionCache || !enableDebounce ? rawImageRequest : debouncedImageRequest;
|
||||||
|
|
||||||
const imageCacheKey = src ?? rawImageRequest?.cacheKey;
|
|
||||||
const trackLoadedInInstance = enableViewport;
|
|
||||||
|
|
||||||
const [hasLoadedInInstance, setHasLoadedInInstance] = useState(false);
|
const [hasLoadedInInstance, setHasLoadedInInstance] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!trackLoadedInInstance) {
|
setHasLoadedInInstance(false);
|
||||||
return;
|
}, [effectiveImageRequest?.cacheKey]);
|
||||||
}
|
|
||||||
|
|
||||||
setHasLoadedInInstance((current) => (current ? false : current));
|
|
||||||
}, [imageCacheKey, trackLoadedInInstance]);
|
|
||||||
|
|
||||||
const shouldLoadImage = Boolean(
|
const shouldLoadImage = Boolean(
|
||||||
effectiveImageRequest &&
|
effectiveImageRequest &&
|
||||||
@@ -119,13 +112,13 @@ export function BaseImage({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!trackLoadedInInstance || !nativeImage.isLoaded || !imageCacheKey) {
|
if (!nativeImage.isLoaded || !effectiveImageRequest?.cacheKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadedImageCacheKeys.add(imageCacheKey);
|
loadedImageCacheKeys.add(effectiveImageRequest.cacheKey);
|
||||||
setHasLoadedInInstance((current) => (current ? current : true));
|
setHasLoadedInInstance(true);
|
||||||
}, [imageCacheKey, nativeImage.isLoaded, trackLoadedInInstance]);
|
}, [effectiveImageRequest?.cacheKey, nativeImage.isLoaded]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ImageContainer
|
<ImageContainer
|
||||||
|
|||||||
@@ -26,11 +26,8 @@ export function useNativeImage({
|
|||||||
const loadedRequestSignatureRef = useRef<null | string>(null);
|
const loadedRequestSignatureRef = useRef<null | string>(null);
|
||||||
const objectUrlRef = useRef<null | string>(null);
|
const objectUrlRef = useRef<null | string>(null);
|
||||||
const onFetchErrorRef = useRef(onFetchError);
|
const onFetchErrorRef = useRef(onFetchError);
|
||||||
const requestRef = useRef(request);
|
|
||||||
const [state, setState] = useState<NativeImageState>({ status: 'idle' });
|
const [state, setState] = useState<NativeImageState>({ status: 'idle' });
|
||||||
|
|
||||||
requestRef.current = request;
|
|
||||||
|
|
||||||
const requestSignature = useMemo(() => {
|
const requestSignature = useMemo(() => {
|
||||||
if (!request) {
|
if (!request) {
|
||||||
return null;
|
return null;
|
||||||
@@ -47,8 +44,6 @@ export function useNativeImage({
|
|||||||
onFetchErrorRef.current = onFetchError;
|
onFetchErrorRef.current = onFetchError;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const request = requestRef.current;
|
|
||||||
|
|
||||||
const abortCurrentRequest = () => {
|
const abortCurrentRequest = () => {
|
||||||
abortControllerRef.current?.abort();
|
abortControllerRef.current?.abort();
|
||||||
abortControllerRef.current = null;
|
abortControllerRef.current = null;
|
||||||
@@ -67,61 +62,28 @@ export function useNativeImage({
|
|||||||
if (!request || !requestSignature) {
|
if (!request || !requestSignature) {
|
||||||
abortCurrentRequest();
|
abortCurrentRequest();
|
||||||
revokeObjectUrl();
|
revokeObjectUrl();
|
||||||
setState((currentState) => {
|
setState({ status: 'idle' });
|
||||||
if (currentState.status === 'idle' && !currentState.displaySrc) {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { status: 'idle' };
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
abortCurrentRequest();
|
abortCurrentRequest();
|
||||||
setState((currentState) => {
|
setState((currentState) =>
|
||||||
if (currentState.displaySrc) {
|
currentState.displaySrc
|
||||||
if (currentState.status === 'loaded') {
|
? { ...currentState, status: 'loaded' }
|
||||||
return currentState;
|
: { status: 'idle' },
|
||||||
}
|
);
|
||||||
|
|
||||||
return { ...currentState, status: 'loaded' };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentState.status === 'idle' && !currentState.displaySrc) {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { status: 'idle' };
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadedRequestSignatureRef.current === requestSignature && objectUrlRef.current) {
|
if (loadedRequestSignatureRef.current === requestSignature && objectUrlRef.current) {
|
||||||
const cachedObjectUrl = objectUrlRef.current;
|
setState({ displaySrc: objectUrlRef.current, status: 'loaded' });
|
||||||
|
|
||||||
setState((currentState) => {
|
|
||||||
if (
|
|
||||||
currentState.status === 'loaded' &&
|
|
||||||
currentState.displaySrc === cachedObjectUrl
|
|
||||||
) {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { displaySrc: cachedObjectUrl, status: 'loaded' };
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
abortCurrentRequest();
|
abortCurrentRequest();
|
||||||
revokeObjectUrl();
|
revokeObjectUrl();
|
||||||
setState((currentState) => {
|
setState({ status: 'loading' });
|
||||||
if (currentState.status === 'loading' && !currentState.displaySrc) {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { status: 'loading' };
|
|
||||||
});
|
|
||||||
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
abortControllerRef.current = abortController;
|
abortControllerRef.current = abortController;
|
||||||
@@ -176,7 +138,7 @@ export function useNativeImage({
|
|||||||
abortControllerRef.current = null;
|
abortControllerRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [enabled, fetchPriority, requestSignature]);
|
}, [enabled, fetchPriority, request, requestSignature]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user