mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
add 10s retry for playback on network err (#1779)
This commit is contained in:
@@ -224,6 +224,7 @@
|
|||||||
"notificationDenied": "permissions for notifications were denied. this setting has no effect",
|
"notificationDenied": "permissions for notifications were denied. this setting has no effect",
|
||||||
"openError": "could not open file",
|
"openError": "could not open file",
|
||||||
"playbackError": "an error occurred when trying to play the media",
|
"playbackError": "an error occurred when trying to play the media",
|
||||||
|
"playbackPausedDueToError": "playback was paused due to an error",
|
||||||
"remoteDisableError": "an error occurred when trying to $t(common.disable) the remote server",
|
"remoteDisableError": "an error occurred when trying to $t(common.disable) the remote server",
|
||||||
"remoteEnableError": "an error occurred when trying to $t(common.enable) the remote server",
|
"remoteEnableError": "an error occurred when trying to $t(common.enable) the remote server",
|
||||||
"remotePortError": "an error occurred when trying to set the remote server port",
|
"remotePortError": "an error occurred when trying to set the remote server port",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ interface WebPlayerEngineProps {
|
|||||||
isTransitioning: boolean;
|
isTransitioning: boolean;
|
||||||
onEndedPlayer1: () => void;
|
onEndedPlayer1: () => void;
|
||||||
onEndedPlayer2: () => void;
|
onEndedPlayer2: () => void;
|
||||||
|
onErrorPause: () => void;
|
||||||
onProgressPlayer1: (e: PlayerOnProgressProps) => void;
|
onProgressPlayer1: (e: PlayerOnProgressProps) => void;
|
||||||
onProgressPlayer2: (e: PlayerOnProgressProps) => void;
|
onProgressPlayer2: (e: PlayerOnProgressProps) => void;
|
||||||
onStartedPlayer1: (player: ReactPlayer) => void;
|
onStartedPlayer1: (player: ReactPlayer) => void;
|
||||||
@@ -39,6 +40,9 @@ interface WebPlayerEngineProps {
|
|||||||
volume: number;
|
volume: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_NETWORK_RETRIES = 5;
|
||||||
|
const NETWORK_RETRY_DELAY_MS = 2000;
|
||||||
|
|
||||||
// Credits: https://gist.github.com/novwhisky/8a1a0168b94f3b6abfaa?permalink_comment_id=1551393#gistcomment-1551393
|
// Credits: https://gist.github.com/novwhisky/8a1a0168b94f3b6abfaa?permalink_comment_id=1551393#gistcomment-1551393
|
||||||
// This is used so that the player will always have an <audio> element. This means that
|
// This is used so that the player will always have an <audio> element. This means that
|
||||||
// player1Source and player2Source are connected BEFORE the user presses play for
|
// player1Source and player2Source are connected BEFORE the user presses play for
|
||||||
@@ -53,6 +57,7 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
|||||||
isTransitioning,
|
isTransitioning,
|
||||||
onEndedPlayer1,
|
onEndedPlayer1,
|
||||||
onEndedPlayer2,
|
onEndedPlayer2,
|
||||||
|
onErrorPause,
|
||||||
onProgressPlayer1,
|
onProgressPlayer1,
|
||||||
onProgressPlayer2,
|
onProgressPlayer2,
|
||||||
onStartedPlayer1,
|
onStartedPlayer1,
|
||||||
@@ -69,6 +74,8 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
|||||||
|
|
||||||
const player1Ref = useRef<null | ReactPlayer>(null);
|
const player1Ref = useRef<null | ReactPlayer>(null);
|
||||||
const player2Ref = useRef<null | ReactPlayer>(null);
|
const player2Ref = useRef<null | ReactPlayer>(null);
|
||||||
|
const networkRetryCount1 = useRef(0);
|
||||||
|
const networkRetryCount2 = useRef(0);
|
||||||
const [ReactPlayerComponent, setReactPlayerComponent] = useState<any>(null);
|
const [ReactPlayerComponent, setReactPlayerComponent] = useState<any>(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
@@ -150,7 +157,12 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
|||||||
const volume1 = convertToLogVolume(internalVolume1);
|
const volume1 = convertToLogVolume(internalVolume1);
|
||||||
const volume2 = convertToLogVolume(internalVolume2);
|
const volume2 = convertToLogVolume(internalVolume2);
|
||||||
|
|
||||||
const handleOnError = (playerRef: React.RefObject<null | ReactPlayer>, onEnded: () => void) => {
|
const handleOnError = (
|
||||||
|
playerRef: React.RefObject<null | ReactPlayer>,
|
||||||
|
onEnded: () => void,
|
||||||
|
onErrorPause: () => void,
|
||||||
|
networkRetryCountRef: React.RefObject<number>,
|
||||||
|
) => {
|
||||||
return ({ target }: ErrorEvent) => {
|
return ({ target }: ErrorEvent) => {
|
||||||
const { current: player } = playerRef;
|
const { current: player } = playerRef;
|
||||||
|
|
||||||
@@ -165,17 +177,46 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
|||||||
meta: { error },
|
meta: { error },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
const isNetworkError =
|
||||||
error?.code !== MediaError.MEDIA_ERR_DECODE &&
|
error?.code === MediaError.MEDIA_ERR_NETWORK ||
|
||||||
error?.code !== MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
|
error?.code === MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED;
|
||||||
) {
|
|
||||||
|
if (isNetworkError) {
|
||||||
|
if (networkRetryCountRef.current < MAX_NETWORK_RETRIES) {
|
||||||
|
networkRetryCountRef.current += 1;
|
||||||
|
const audio = target;
|
||||||
|
setTimeout(() => {
|
||||||
|
audio.load();
|
||||||
|
audio.play().catch(() => {
|
||||||
|
logFn.error(logMsg[LogCategory.PLAYER].playbackError, {
|
||||||
|
category: LogCategory.PLAYER,
|
||||||
|
meta: { error: 'Failed to play audio after network error' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, NETWORK_RETRY_DELAY_MS);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error?.code !== MediaError.MEDIA_ERR_DECODE && !isNetworkError) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error?.code === MediaError.MEDIA_ERR_DECODE) {
|
||||||
onEnded();
|
onEnded();
|
||||||
|
} else {
|
||||||
|
if (onErrorPause) {
|
||||||
|
onErrorPause();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
networkRetryCount1.current = 0;
|
||||||
|
networkRetryCount2.current = 0;
|
||||||
|
}, [src1, src2]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const player1 = player1Ref.current?.getInternalPlayer();
|
const player1 = player1Ref.current?.getInternalPlayer();
|
||||||
if (player1 && player1 instanceof HTMLAudioElement) {
|
if (player1 && player1 instanceof HTMLAudioElement) {
|
||||||
@@ -224,7 +265,12 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
|||||||
id="web-player-1"
|
id="web-player-1"
|
||||||
muted={isMuted}
|
muted={isMuted}
|
||||||
onEnded={src1 ? () => onEndedPlayer1() : undefined}
|
onEnded={src1 ? () => onEndedPlayer1() : undefined}
|
||||||
onError={handleOnError(player1Ref, () => onEndedPlayer1())}
|
onError={handleOnError(
|
||||||
|
player1Ref,
|
||||||
|
() => onEndedPlayer1(),
|
||||||
|
onErrorPause,
|
||||||
|
networkRetryCount1,
|
||||||
|
)}
|
||||||
onProgress={onProgressPlayer1}
|
onProgress={onProgressPlayer1}
|
||||||
onReady={handleOnReadyPlayer1}
|
onReady={handleOnReadyPlayer1}
|
||||||
playbackRate={speed || 1}
|
playbackRate={speed || 1}
|
||||||
@@ -244,7 +290,12 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
|||||||
id="web-player-2"
|
id="web-player-2"
|
||||||
muted={isMuted}
|
muted={isMuted}
|
||||||
onEnded={src2 ? () => onEndedPlayer2() : undefined}
|
onEnded={src2 ? () => onEndedPlayer2() : undefined}
|
||||||
onError={handleOnError(player2Ref, () => onEndedPlayer2())}
|
onError={handleOnError(
|
||||||
|
player2Ref,
|
||||||
|
() => onEndedPlayer2(),
|
||||||
|
onErrorPause,
|
||||||
|
networkRetryCount2,
|
||||||
|
)}
|
||||||
onProgress={onProgressPlayer2}
|
onProgress={onProgressPlayer2}
|
||||||
onReady={handleOnReadyPlayer2}
|
onReady={handleOnReadyPlayer2}
|
||||||
playbackRate={speed || 1}
|
playbackRate={speed || 1}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Dispatch } from 'react';
|
|||||||
import type ReactPlayer from 'react-player';
|
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 {
|
import {
|
||||||
WebPlayerEngine,
|
WebPlayerEngine,
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
usePlayerProperties,
|
usePlayerProperties,
|
||||||
usePlayerVolume,
|
usePlayerVolume,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
|
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, PlayerStatus, PlayerStyle } from '/@/shared/types/types';
|
||||||
|
|
||||||
@@ -29,8 +31,9 @@ const PLAY_PAUSE_FADE_INTERVAL = 10;
|
|||||||
|
|
||||||
export function WebPlayer() {
|
export function WebPlayer() {
|
||||||
const playerRef = useRef<null | WebPlayerEngineHandle>(null);
|
const playerRef = useRef<null | WebPlayerEngineHandle>(null);
|
||||||
|
const { t } = useTranslation();
|
||||||
const { num, player1, player2, status } = usePlayerData();
|
const { num, player1, player2, status } = usePlayerData();
|
||||||
const { mediaAutoNext, setTimestamp } = usePlayerActions();
|
const { mediaAutoNext, mediaPause, setTimestamp } = usePlayerActions();
|
||||||
const playback = useMpvSettings();
|
const playback = useMpvSettings();
|
||||||
const { webAudio } = useWebAudio();
|
const { webAudio } = useWebAudio();
|
||||||
|
|
||||||
@@ -443,12 +446,20 @@ export function WebPlayer() {
|
|||||||
[player2Source, player2Url, webAudio],
|
[player2Source, player2Url, webAudio],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleOnErrorPause = useCallback(() => {
|
||||||
|
mediaPause();
|
||||||
|
toast.error({
|
||||||
|
message: t('error.playbackPausedDueToError', { postProcess: 'sentenceCase' }),
|
||||||
|
});
|
||||||
|
}, [mediaPause, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<WebPlayerEngine
|
<WebPlayerEngine
|
||||||
isMuted={isMuted}
|
isMuted={isMuted}
|
||||||
isTransitioning={Boolean(isTransitioning)}
|
isTransitioning={Boolean(isTransitioning)}
|
||||||
onEndedPlayer1={handleOnEndedPlayer1}
|
onEndedPlayer1={handleOnEndedPlayer1}
|
||||||
onEndedPlayer2={handleOnEndedPlayer2}
|
onEndedPlayer2={handleOnEndedPlayer2}
|
||||||
|
onErrorPause={handleOnErrorPause}
|
||||||
onProgressPlayer1={onProgressPlayer1}
|
onProgressPlayer1={onProgressPlayer1}
|
||||||
onProgressPlayer2={onProgressPlayer2}
|
onProgressPlayer2={onProgressPlayer2}
|
||||||
onStartedPlayer1={handlePlayer1Start}
|
onStartedPlayer1={handlePlayer1Start}
|
||||||
|
|||||||
Reference in New Issue
Block a user