mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10: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",
|
||||
"openError": "could not open file",
|
||||
"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",
|
||||
"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",
|
||||
|
||||
@@ -25,6 +25,7 @@ interface WebPlayerEngineProps {
|
||||
isTransitioning: boolean;
|
||||
onEndedPlayer1: () => void;
|
||||
onEndedPlayer2: () => void;
|
||||
onErrorPause: () => void;
|
||||
onProgressPlayer1: (e: PlayerOnProgressProps) => void;
|
||||
onProgressPlayer2: (e: PlayerOnProgressProps) => void;
|
||||
onStartedPlayer1: (player: ReactPlayer) => void;
|
||||
@@ -39,6 +40,9 @@ interface WebPlayerEngineProps {
|
||||
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
|
||||
// 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
|
||||
@@ -53,6 +57,7 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
||||
isTransitioning,
|
||||
onEndedPlayer1,
|
||||
onEndedPlayer2,
|
||||
onErrorPause,
|
||||
onProgressPlayer1,
|
||||
onProgressPlayer2,
|
||||
onStartedPlayer1,
|
||||
@@ -69,6 +74,8 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
||||
|
||||
const player1Ref = 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 [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
@@ -150,7 +157,12 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
||||
const volume1 = convertToLogVolume(internalVolume1);
|
||||
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) => {
|
||||
const { current: player } = playerRef;
|
||||
|
||||
@@ -165,17 +177,46 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
||||
meta: { error },
|
||||
});
|
||||
|
||||
if (
|
||||
error?.code !== MediaError.MEDIA_ERR_DECODE &&
|
||||
error?.code !== MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
|
||||
) {
|
||||
const isNetworkError =
|
||||
error?.code === MediaError.MEDIA_ERR_NETWORK ||
|
||||
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;
|
||||
}
|
||||
|
||||
onEnded();
|
||||
if (error?.code === MediaError.MEDIA_ERR_DECODE) {
|
||||
onEnded();
|
||||
} else {
|
||||
if (onErrorPause) {
|
||||
onErrorPause();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
networkRetryCount1.current = 0;
|
||||
networkRetryCount2.current = 0;
|
||||
}, [src1, src2]);
|
||||
|
||||
useEffect(() => {
|
||||
const player1 = player1Ref.current?.getInternalPlayer();
|
||||
if (player1 && player1 instanceof HTMLAudioElement) {
|
||||
@@ -224,7 +265,12 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
||||
id="web-player-1"
|
||||
muted={isMuted}
|
||||
onEnded={src1 ? () => onEndedPlayer1() : undefined}
|
||||
onError={handleOnError(player1Ref, () => onEndedPlayer1())}
|
||||
onError={handleOnError(
|
||||
player1Ref,
|
||||
() => onEndedPlayer1(),
|
||||
onErrorPause,
|
||||
networkRetryCount1,
|
||||
)}
|
||||
onProgress={onProgressPlayer1}
|
||||
onReady={handleOnReadyPlayer1}
|
||||
playbackRate={speed || 1}
|
||||
@@ -244,7 +290,12 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
||||
id="web-player-2"
|
||||
muted={isMuted}
|
||||
onEnded={src2 ? () => onEndedPlayer2() : undefined}
|
||||
onError={handleOnError(player2Ref, () => onEndedPlayer2())}
|
||||
onError={handleOnError(
|
||||
player2Ref,
|
||||
() => onEndedPlayer2(),
|
||||
onErrorPause,
|
||||
networkRetryCount2,
|
||||
)}
|
||||
onProgress={onProgressPlayer2}
|
||||
onReady={handleOnReadyPlayer2}
|
||||
playbackRate={speed || 1}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Dispatch } from 'react';
|
||||
import type ReactPlayer from 'react-player';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
WebPlayerEngine,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
usePlayerProperties,
|
||||
usePlayerVolume,
|
||||
} from '/@/renderer/store';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { QueueSong } from '/@/shared/types/domain-types';
|
||||
import { CrossfadeStyle, PlayerStatus, PlayerStyle } from '/@/shared/types/types';
|
||||
|
||||
@@ -29,8 +31,9 @@ const PLAY_PAUSE_FADE_INTERVAL = 10;
|
||||
|
||||
export function WebPlayer() {
|
||||
const playerRef = useRef<null | WebPlayerEngineHandle>(null);
|
||||
const { t } = useTranslation();
|
||||
const { num, player1, player2, status } = usePlayerData();
|
||||
const { mediaAutoNext, setTimestamp } = usePlayerActions();
|
||||
const { mediaAutoNext, mediaPause, setTimestamp } = usePlayerActions();
|
||||
const playback = useMpvSettings();
|
||||
const { webAudio } = useWebAudio();
|
||||
|
||||
@@ -443,12 +446,20 @@ export function WebPlayer() {
|
||||
[player2Source, player2Url, webAudio],
|
||||
);
|
||||
|
||||
const handleOnErrorPause = useCallback(() => {
|
||||
mediaPause();
|
||||
toast.error({
|
||||
message: t('error.playbackPausedDueToError', { postProcess: 'sentenceCase' }),
|
||||
});
|
||||
}, [mediaPause, t]);
|
||||
|
||||
return (
|
||||
<WebPlayerEngine
|
||||
isMuted={isMuted}
|
||||
isTransitioning={Boolean(isTransitioning)}
|
||||
onEndedPlayer1={handleOnEndedPlayer1}
|
||||
onEndedPlayer2={handleOnEndedPlayer2}
|
||||
onErrorPause={handleOnErrorPause}
|
||||
onProgressPlayer1={onProgressPlayer1}
|
||||
onProgressPlayer2={onProgressPlayer2}
|
||||
onStartedPlayer1={handlePlayer1Start}
|
||||
|
||||
Reference in New Issue
Block a user