mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
support ytmusic controls on web/mpv players
This commit is contained in:
@@ -10,8 +10,10 @@ async function searchYoutube(query: string): Promise<Array<{ type: string; video
|
||||
export const youtubeQueries = {
|
||||
search: (args: { query: string }) => {
|
||||
return queryOptions({
|
||||
gcTime: 1000 * 60 * 1,
|
||||
queryFn: () => searchYoutube(args.query),
|
||||
queryKey: ['youtube', 'search', args.query],
|
||||
staleTime: 1000 * 60 * 1,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -21,8 +21,10 @@ import { PlayerStatus } from '/@/shared/types/types';
|
||||
export interface MpvPlayerEngineHandle extends AudioPlayer {}
|
||||
|
||||
interface MpvPlayerEngineProps {
|
||||
currentSongUrl: string | undefined;
|
||||
isMuted: boolean;
|
||||
isTransitioning: boolean;
|
||||
nextSongUrl: string | undefined;
|
||||
onEnded: () => void;
|
||||
onProgress: (e: PlayerOnProgressProps) => void;
|
||||
playerRef: RefObject<MpvPlayerEngineHandle | null>;
|
||||
@@ -39,8 +41,10 @@ const PROGRESS_UPDATE_INTERVAL = 250;
|
||||
|
||||
export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
const {
|
||||
currentSongUrl: currentSongUrlProp,
|
||||
isMuted,
|
||||
isTransitioning,
|
||||
nextSongUrl: nextSongUrlProp,
|
||||
onEnded,
|
||||
onProgress,
|
||||
playerRef,
|
||||
@@ -56,6 +60,11 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
const isInitializedRef = useRef<boolean>(false);
|
||||
const hasPopulatedQueueRef = useRef<boolean>(false);
|
||||
const isMountedRef = useRef<boolean>(true);
|
||||
const currentSongUrlRef = useRef<string | undefined>(currentSongUrlProp);
|
||||
const nextSongUrlRef = useRef<string | undefined>(nextSongUrlProp);
|
||||
|
||||
currentSongUrlRef.current = currentSongUrlProp;
|
||||
nextSongUrlRef.current = nextSongUrlProp;
|
||||
|
||||
const { mpvAudioDeviceId, transcode } = usePlaybackSettings();
|
||||
const mpvExtraParameters = useSettingsStore((store) => store.playback.mpvExtraParameters);
|
||||
@@ -124,15 +133,17 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
|
||||
if (!radioState.currentStreamUrl) {
|
||||
const playerData = usePlayerStore.getState().getPlayerData();
|
||||
const currentSongUrl = playerData.currentSong
|
||||
? getSongUrl(playerData.currentSong, transcode)
|
||||
: undefined;
|
||||
const nextSongUrl = playerData.nextSong
|
||||
? getSongUrl(playerData.nextSong, transcode)
|
||||
: undefined;
|
||||
const currentResolved =
|
||||
currentSongUrlProp ??
|
||||
(playerData.currentSong
|
||||
? getSongUrl(playerData.currentSong, transcode)
|
||||
: undefined);
|
||||
const nextResolved =
|
||||
nextSongUrlProp ??
|
||||
(playerData.nextSong ? getSongUrl(playerData.nextSong, transcode) : undefined);
|
||||
|
||||
if (currentSongUrl && nextSongUrl && !hasPopulatedQueueRef.current && mpvPlayer) {
|
||||
mpvPlayer.setQueue(currentSongUrl, nextSongUrl, true);
|
||||
if (currentResolved && !hasPopulatedQueueRef.current && mpvPlayer) {
|
||||
mpvPlayer.setQueue(currentResolved, nextResolved ?? currentResolved, true);
|
||||
hasPopulatedQueueRef.current = true;
|
||||
}
|
||||
}
|
||||
@@ -157,6 +168,30 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [mpvExtraParameters, mpvProperties, mpvAudioDeviceId, reloadTrigger]);
|
||||
|
||||
// Sync queue when current/next song URLs change (e.g. user selects song, or external URL resolves from useSongUrl)
|
||||
useEffect(() => {
|
||||
if (!mpvPlayer || !isInitializedRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const radioState = useRadioStore.getState();
|
||||
if (radioState.currentStreamUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const playerData = usePlayerStore.getState().getPlayerData();
|
||||
const currentResolved =
|
||||
currentSongUrlProp ??
|
||||
(playerData.currentSong ? getSongUrl(playerData.currentSong, transcode) : undefined);
|
||||
const nextResolved =
|
||||
nextSongUrlProp ??
|
||||
(playerData.nextSong ? getSongUrl(playerData.nextSong, transcode) : undefined);
|
||||
|
||||
if (currentResolved) {
|
||||
mpvPlayer.setQueue(currentResolved, nextResolved ?? currentResolved, false);
|
||||
}
|
||||
}, [currentSongUrlProp, nextSongUrlProp, currentSong?.id, currentSong?._uniqueId, transcode]);
|
||||
|
||||
// Update volume
|
||||
useEffect(() => {
|
||||
if (!mpvPlayer) {
|
||||
@@ -257,7 +292,7 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
|
||||
const handleOnAutoNext = () => {
|
||||
mediaAutoNext();
|
||||
handleMpvAutoNext(transcode);
|
||||
handleMpvAutoNext(transcode, nextSongUrlRef.current);
|
||||
};
|
||||
|
||||
mpvPlayerListener.rendererAutoNext(handleOnAutoNext);
|
||||
@@ -270,10 +305,10 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
usePlayerEvents(
|
||||
{
|
||||
onMediaNext: () => {
|
||||
replaceMpvQueue(transcode);
|
||||
replaceMpvQueue(transcode, currentSongUrlRef.current, nextSongUrlRef.current);
|
||||
},
|
||||
onMediaPrev: () => {
|
||||
replaceMpvQueue(transcode);
|
||||
replaceMpvQueue(transcode, currentSongUrlRef.current, nextSongUrlRef.current);
|
||||
},
|
||||
onNextSongInsertion: (song) => {
|
||||
const radioState = useRadioStore.getState();
|
||||
@@ -282,11 +317,12 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSongUrl = song ? getSongUrl(song, transcode) : undefined;
|
||||
const nextSongUrl =
|
||||
nextSongUrlRef.current ?? (song ? getSongUrl(song, transcode) : undefined);
|
||||
mpvPlayer?.setQueueNext(nextSongUrl);
|
||||
},
|
||||
onPlayerPlay: () => {
|
||||
replaceMpvQueue(transcode);
|
||||
replaceMpvQueue(transcode, currentSongUrlRef.current, nextSongUrlRef.current);
|
||||
},
|
||||
onQueueCleared: () => {},
|
||||
},
|
||||
@@ -337,24 +373,30 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
|
||||
MpvPlayerEngine.displayName = 'MpvPlayerEngine';
|
||||
|
||||
function handleMpvAutoNext(transcode: {
|
||||
bitrate?: number | undefined;
|
||||
enabled: boolean;
|
||||
format?: string | undefined;
|
||||
}) {
|
||||
function handleMpvAutoNext(
|
||||
transcode: {
|
||||
bitrate?: number | undefined;
|
||||
enabled: boolean;
|
||||
format?: string | undefined;
|
||||
},
|
||||
nextUrlOverride?: string,
|
||||
) {
|
||||
const playerData = usePlayerStore.getState().getPlayerData();
|
||||
const nextSongUrl = playerData.nextSong
|
||||
? getSongUrl(playerData.nextSong, transcode)
|
||||
: undefined;
|
||||
const nextSongUrl =
|
||||
nextUrlOverride ??
|
||||
(playerData.nextSong ? getSongUrl(playerData.nextSong, transcode) : undefined);
|
||||
mpvPlayer?.autoNext(nextSongUrl);
|
||||
}
|
||||
|
||||
function replaceMpvQueue(transcode: {
|
||||
bitrate?: number | undefined;
|
||||
enabled: boolean;
|
||||
format?: string | undefined;
|
||||
}) {
|
||||
// Don't override queue if radio is active
|
||||
function replaceMpvQueue(
|
||||
transcode: {
|
||||
bitrate?: number | undefined;
|
||||
enabled: boolean;
|
||||
format?: string | undefined;
|
||||
},
|
||||
currentUrlOverride?: string,
|
||||
nextUrlOverride?: string,
|
||||
) {
|
||||
const radioState = useRadioStore.getState();
|
||||
|
||||
if (radioState.currentStreamUrl) {
|
||||
@@ -362,11 +404,14 @@ function replaceMpvQueue(transcode: {
|
||||
}
|
||||
|
||||
const playerData = usePlayerStore.getState().getPlayerData();
|
||||
const currentSongUrl = playerData.currentSong
|
||||
? getSongUrl(playerData.currentSong, transcode)
|
||||
: undefined;
|
||||
const nextSongUrl = playerData.nextSong
|
||||
? getSongUrl(playerData.nextSong, transcode)
|
||||
: undefined;
|
||||
mpvPlayer?.setQueue(currentSongUrl, nextSongUrl, false);
|
||||
const currentSongUrl =
|
||||
currentUrlOverride ??
|
||||
(playerData.currentSong ? getSongUrl(playerData.currentSong, transcode) : undefined);
|
||||
const nextSongUrl =
|
||||
nextUrlOverride ??
|
||||
(playerData.nextSong ? getSongUrl(playerData.nextSong, transcode) : undefined);
|
||||
|
||||
if (currentSongUrl) {
|
||||
mpvPlayer?.setQueue(currentSongUrl, nextSongUrl ?? currentSongUrl, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,17 @@ import { logMsg } from '/@/renderer/utils/logger-message';
|
||||
import { PlayerStatus } from '/@/shared/types/types';
|
||||
|
||||
export interface WebPlayerEngineHandle extends AudioPlayer {
|
||||
player1(): {
|
||||
ref: null | ReactPlayer;
|
||||
setVolume: (volume: number) => void;
|
||||
};
|
||||
player2(): {
|
||||
ref: null | ReactPlayer;
|
||||
setVolume: (volume: number) => void;
|
||||
};
|
||||
player1(): WebPlayerEnginePlayerHandle;
|
||||
player2(): WebPlayerEnginePlayerHandle;
|
||||
}
|
||||
|
||||
export interface WebPlayerEnginePlayerHandle {
|
||||
getCurrentTime: () => number;
|
||||
getDuration: () => number;
|
||||
pause: () => void;
|
||||
play: () => void;
|
||||
ref: null | ReactPlayer;
|
||||
setVolume: (volume: number) => void;
|
||||
}
|
||||
|
||||
interface WebPlayerEngineProps {
|
||||
@@ -39,6 +42,70 @@ interface WebPlayerEngineProps {
|
||||
volume: number;
|
||||
}
|
||||
|
||||
interface YouTubePlayer {
|
||||
getCurrentTime?: () => number;
|
||||
getDuration?: () => number;
|
||||
pauseVideo?: () => void;
|
||||
playVideo?: () => void;
|
||||
}
|
||||
|
||||
function getInternalCurrentTime(ref: null | ReactPlayer): number {
|
||||
const internal = ref?.getInternalPlayer();
|
||||
if (!internal) return 0;
|
||||
if (internal instanceof HTMLMediaElement) {
|
||||
return (internal as HTMLMediaElement).currentTime ?? 0;
|
||||
}
|
||||
if (isYouTubePlayer(internal) && typeof internal.getCurrentTime === 'function') {
|
||||
return internal.getCurrentTime() ?? 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getInternalDuration(ref: null | ReactPlayer): number {
|
||||
const internal = ref?.getInternalPlayer();
|
||||
if (!internal) return 0;
|
||||
if (internal instanceof HTMLMediaElement) {
|
||||
return (internal as HTMLMediaElement).duration ?? 0;
|
||||
}
|
||||
if (isYouTubePlayer(internal) && typeof internal.getDuration === 'function') {
|
||||
return internal.getDuration() ?? 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isYouTubePlayer(internal: unknown): internal is YouTubePlayer {
|
||||
return (
|
||||
typeof internal === 'object' &&
|
||||
internal !== null &&
|
||||
'playVideo' in internal &&
|
||||
typeof (internal as YouTubePlayer).playVideo === 'function'
|
||||
);
|
||||
}
|
||||
|
||||
function pauseInternalPlayer(ref: null | ReactPlayer): void {
|
||||
const internal = ref?.getInternalPlayer();
|
||||
if (!internal) return;
|
||||
if (internal instanceof HTMLMediaElement) {
|
||||
(internal as HTMLMediaElement).pause();
|
||||
return;
|
||||
}
|
||||
if (isYouTubePlayer(internal)) {
|
||||
internal.pauseVideo?.();
|
||||
}
|
||||
}
|
||||
|
||||
function playInternalPlayer(ref: null | ReactPlayer): void {
|
||||
const internal = ref?.getInternalPlayer();
|
||||
if (!internal) return;
|
||||
if (internal instanceof HTMLMediaElement) {
|
||||
void (internal as HTMLMediaElement).play().catch(() => {});
|
||||
return;
|
||||
}
|
||||
if (isYouTubePlayer(internal)) {
|
||||
internal.playVideo?.();
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -108,25 +175,33 @@ export const WebPlayerEngine = (props: WebPlayerEngineProps) => {
|
||||
setInternalVolume2(Math.min(1, internalVolume2 + by / 100));
|
||||
},
|
||||
pause() {
|
||||
player1Ref.current?.getInternalPlayer()?.pause();
|
||||
player2Ref.current?.getInternalPlayer()?.pause();
|
||||
pauseInternalPlayer(player1Ref.current);
|
||||
pauseInternalPlayer(player2Ref.current);
|
||||
},
|
||||
play() {
|
||||
if (playerNum === 1) {
|
||||
player1Ref.current?.getInternalPlayer()?.play();
|
||||
playInternalPlayer(player1Ref.current);
|
||||
} else {
|
||||
player2Ref.current?.getInternalPlayer()?.play();
|
||||
playInternalPlayer(player2Ref.current);
|
||||
}
|
||||
},
|
||||
player1() {
|
||||
player1(): WebPlayerEnginePlayerHandle {
|
||||
return {
|
||||
ref: player1Ref?.current,
|
||||
getCurrentTime: () => getInternalCurrentTime(player1Ref.current),
|
||||
getDuration: () => getInternalDuration(player1Ref.current),
|
||||
pause: () => pauseInternalPlayer(player1Ref.current),
|
||||
play: () => playInternalPlayer(player1Ref.current),
|
||||
ref: player1Ref?.current ?? null,
|
||||
setVolume: (volume: number) => setInternalVolume1(volume / 100 || 0),
|
||||
};
|
||||
},
|
||||
player2() {
|
||||
player2(): WebPlayerEnginePlayerHandle {
|
||||
return {
|
||||
ref: player2Ref?.current,
|
||||
getCurrentTime: () => getInternalCurrentTime(player2Ref.current),
|
||||
getDuration: () => getInternalDuration(player2Ref.current),
|
||||
pause: () => pauseInternalPlayer(player2Ref.current),
|
||||
play: () => playInternalPlayer(player2Ref.current),
|
||||
ref: player2Ref?.current ?? null,
|
||||
setVolume: (volume: number) => setInternalVolume2(volume / 100 || 0),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -30,6 +30,7 @@ export function useSongUrl(
|
||||
return prior.current[1];
|
||||
}
|
||||
const url = getYoutubeUrlFromSearchResults(youtubeSearch.data);
|
||||
|
||||
if (url) prior.current = [song._uniqueId, url];
|
||||
return url;
|
||||
}, [song, isExternal, current, youtubeSearch.data]);
|
||||
@@ -81,12 +82,16 @@ function getYoutubeUrlFromSearchResults(
|
||||
): string | undefined {
|
||||
if (!results?.length) return undefined;
|
||||
const first = results.find((r) => r.type === 'SONG' || r.type === 'VIDEO');
|
||||
|
||||
return first && 'videoId' in first && first.videoId
|
||||
? `${YOUTUBE_WATCH_BASE}${first.videoId}`
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export const getSongUrl = (song: QueueSong, transcode: TranscodingConfig) => {
|
||||
export const getSongUrl = (song: QueueSong, transcode: TranscodingConfig): string => {
|
||||
if (song._serverType === ServerType.EXTERNAL) {
|
||||
return '';
|
||||
}
|
||||
return api.controller.getStreamUrl({
|
||||
apiClientProps: { serverId: song._serverId },
|
||||
query: {
|
||||
@@ -97,3 +102,32 @@ export const getSongUrl = (song: QueueSong, transcode: TranscodingConfig) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export async function getSongUrlAsync(
|
||||
song: QueueSong | undefined,
|
||||
transcode: TranscodingConfig,
|
||||
): Promise<string | undefined> {
|
||||
if (!song) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (song._serverType === ServerType.EXTERNAL) {
|
||||
if (typeof window === 'undefined' || !window.api?.youtube) {
|
||||
return undefined;
|
||||
}
|
||||
const searchQuery = `${song.artistName ?? ''} ${song.name ?? ''}`.trim();
|
||||
if (!searchQuery) {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
const results = await window.api.youtube.search(searchQuery);
|
||||
console.log('results', results);
|
||||
return getYoutubeUrlFromSearchResults(results);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const url = getSongUrl(song, transcode);
|
||||
return url || undefined;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { MpvPlayerEngine, MpvPlayerEngineHandle } from './engine/mpv-player-engine';
|
||||
|
||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
||||
import { useSongUrl } from '/@/renderer/features/player/audio-player/hooks/use-stream-url';
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import {
|
||||
usePlaybackSettings,
|
||||
@@ -23,12 +24,15 @@ const mpvPlayer = isElectron() ? window.api.mpvPlayer : null;
|
||||
|
||||
export function MpvPlayer() {
|
||||
const playerRef = useRef<MpvPlayerEngineHandle>(null);
|
||||
const { currentSong, status } = usePlayerData();
|
||||
const { currentSong, nextSong, status } = usePlayerData();
|
||||
const { mediaAutoNext, setTimestamp } = usePlayerActions();
|
||||
const { speed } = usePlayerProperties();
|
||||
const isMuted = usePlayerMuted();
|
||||
const volume = usePlayerVolume();
|
||||
const { audioFadeOnStatusChange } = usePlaybackSettings();
|
||||
const { audioFadeOnStatusChange, transcode } = usePlaybackSettings();
|
||||
|
||||
const currentSongUrl = useSongUrl(currentSong, true, transcode);
|
||||
const nextSongUrl = useSongUrl(nextSong, false, transcode);
|
||||
|
||||
const [localPlayerStatus, setLocalPlayerStatus] = useState<PlayerStatus>(status);
|
||||
const [isTransitioning, setIsTransitioning] = useState(false);
|
||||
@@ -174,8 +178,10 @@ export function MpvPlayer() {
|
||||
|
||||
return (
|
||||
<MpvPlayerEngine
|
||||
currentSongUrl={currentSongUrl}
|
||||
isMuted={isMuted}
|
||||
isTransitioning={isTransitioning}
|
||||
nextSongUrl={nextSongUrl}
|
||||
onEnded={handleOnEnded}
|
||||
onProgress={onProgress}
|
||||
playerRef={playerRef}
|
||||
|
||||
@@ -106,7 +106,7 @@ export function WebPlayer() {
|
||||
currentPlayer: playerRef.current.player1(),
|
||||
currentPlayerNum: num,
|
||||
currentTime: e.playedSeconds,
|
||||
duration: getDuration(playerRef.current.player1().ref),
|
||||
duration: getDuration(playerRef.current.player1()),
|
||||
hasNextSong: Boolean(player2),
|
||||
isTransitioning,
|
||||
nextPlayer: playerRef.current.player2(),
|
||||
@@ -118,7 +118,7 @@ export function WebPlayer() {
|
||||
case PlayerStyle.GAPLESS:
|
||||
gaplessHandler({
|
||||
currentTime: e.playedSeconds,
|
||||
duration: getDuration(playerRef.current.player1().ref),
|
||||
duration: getDuration(playerRef.current.player1()),
|
||||
isFlac: false,
|
||||
isTransitioning,
|
||||
nextPlayer: playerRef.current.player2(),
|
||||
@@ -144,7 +144,7 @@ export function WebPlayer() {
|
||||
currentPlayer: playerRef.current.player2(),
|
||||
currentPlayerNum: num,
|
||||
currentTime: e.playedSeconds,
|
||||
duration: getDuration(playerRef.current.player2().ref),
|
||||
duration: getDuration(playerRef.current.player2()),
|
||||
hasNextSong: Boolean(player1),
|
||||
isTransitioning,
|
||||
nextPlayer: playerRef.current.player1(),
|
||||
@@ -156,7 +156,7 @@ export function WebPlayer() {
|
||||
case PlayerStyle.GAPLESS:
|
||||
gaplessHandler({
|
||||
currentTime: e.playedSeconds,
|
||||
duration: getDuration(playerRef.current.player2().ref),
|
||||
duration: getDuration(playerRef.current.player2()),
|
||||
isFlac: false,
|
||||
isTransitioning,
|
||||
nextPlayer: playerRef.current.player1(),
|
||||
@@ -175,7 +175,7 @@ export function WebPlayer() {
|
||||
});
|
||||
|
||||
promise.then(() => {
|
||||
playerRef.current?.player1()?.ref?.getInternalPlayer().pause();
|
||||
playerRef.current?.player1()?.pause();
|
||||
playerRef.current?.setVolume(volume);
|
||||
setIsTransitioning(false);
|
||||
});
|
||||
@@ -188,7 +188,7 @@ export function WebPlayer() {
|
||||
});
|
||||
|
||||
promise.then(() => {
|
||||
playerRef.current?.player2()?.ref?.getInternalPlayer().pause();
|
||||
playerRef.current?.player2()?.pause();
|
||||
playerRef.current?.setVolume(volume);
|
||||
setIsTransitioning(false);
|
||||
});
|
||||
@@ -213,11 +213,11 @@ export function WebPlayer() {
|
||||
if (num === 1) {
|
||||
playerRef.current?.player1()?.setVolume(volume);
|
||||
playerRef.current?.player2()?.setVolume(0);
|
||||
playerRef.current?.player2()?.ref?.getInternalPlayer()?.pause();
|
||||
playerRef.current?.player2()?.pause();
|
||||
} else {
|
||||
playerRef.current?.player2()?.setVolume(volume);
|
||||
playerRef.current?.player1()?.setVolume(0);
|
||||
playerRef.current?.player1()?.ref?.getInternalPlayer()?.pause();
|
||||
playerRef.current?.player1()?.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,11 +241,11 @@ export function WebPlayer() {
|
||||
if (num === 1) {
|
||||
playerRef.current?.player1()?.setVolume(volume);
|
||||
playerRef.current?.player2()?.setVolume(0);
|
||||
playerRef.current?.player2()?.ref?.getInternalPlayer()?.pause();
|
||||
playerRef.current?.player2()?.pause();
|
||||
} else {
|
||||
playerRef.current?.player2()?.setVolume(volume);
|
||||
playerRef.current?.player1()?.setVolume(0);
|
||||
playerRef.current?.player1()?.ref?.getInternalPlayer()?.pause();
|
||||
playerRef.current?.player1()?.pause();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,14 +294,12 @@ export function WebPlayer() {
|
||||
const interval = setInterval(() => {
|
||||
const activePlayer =
|
||||
num === 1 ? playerRef.current?.player1() : playerRef.current?.player2();
|
||||
const internalPlayer =
|
||||
activePlayer?.ref?.getInternalPlayer() as HTMLAudioElement | null;
|
||||
|
||||
if (!internalPlayer) {
|
||||
if (!activePlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTime = internalPlayer.currentTime;
|
||||
const currentTime = activePlayer.getCurrentTime();
|
||||
|
||||
if (
|
||||
transitionType === PlayerStyle.CROSSFADE ||
|
||||
@@ -468,6 +466,7 @@ function crossfadeHandler(args: {
|
||||
crossfadeDuration: number;
|
||||
crossfadeStyle: CrossfadeStyle;
|
||||
currentPlayer: {
|
||||
pause: () => void;
|
||||
ref: null | ReactPlayer;
|
||||
setVolume: (volume: number) => void;
|
||||
};
|
||||
@@ -477,6 +476,8 @@ function crossfadeHandler(args: {
|
||||
hasNextSong: boolean;
|
||||
isTransitioning: boolean | string;
|
||||
nextPlayer: {
|
||||
pause: () => void;
|
||||
play: () => void;
|
||||
ref: null | ReactPlayer;
|
||||
setVolume: (volume: number) => void;
|
||||
};
|
||||
@@ -504,7 +505,7 @@ function crossfadeHandler(args: {
|
||||
if (!hasNextSong) {
|
||||
currentPlayer.setVolume(volume);
|
||||
nextPlayer.setVolume(0);
|
||||
nextPlayer.ref?.getInternalPlayer()?.pause();
|
||||
nextPlayer.pause();
|
||||
|
||||
if (isTransitioning) {
|
||||
setIsTransitioning(false);
|
||||
@@ -516,7 +517,7 @@ function crossfadeHandler(args: {
|
||||
if (!isTransitioning) {
|
||||
if (duration > 0 && currentTime > duration - crossfadeDuration) {
|
||||
nextPlayer.setVolume(0);
|
||||
nextPlayer.ref?.getInternalPlayer().play();
|
||||
nextPlayer.play();
|
||||
return setIsTransitioning(player);
|
||||
}
|
||||
|
||||
@@ -586,6 +587,7 @@ function gaplessHandler(args: {
|
||||
isFlac: boolean;
|
||||
isTransitioning: boolean | string;
|
||||
nextPlayer: {
|
||||
play: () => void;
|
||||
ref: null | ReactPlayer;
|
||||
setVolume: (volume: number) => void;
|
||||
};
|
||||
@@ -604,10 +606,8 @@ function gaplessHandler(args: {
|
||||
const durationPadding = getDurationPadding(isFlac);
|
||||
|
||||
if (currentTime + durationPadding >= duration) {
|
||||
return nextPlayer.ref
|
||||
?.getInternalPlayer()
|
||||
?.play()
|
||||
.catch(() => {});
|
||||
nextPlayer.play();
|
||||
return;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -647,8 +647,14 @@ function getCrossfadeEasing(style: CrossfadeStyle): {
|
||||
}
|
||||
}
|
||||
|
||||
function getDuration(ref: null | ReactPlayer | undefined) {
|
||||
return ref?.getInternalPlayer()?.duration || 0;
|
||||
function getDuration(
|
||||
player:
|
||||
| undefined
|
||||
| {
|
||||
getDuration: () => number;
|
||||
},
|
||||
) {
|
||||
return player?.getDuration?.() ?? 0;
|
||||
}
|
||||
|
||||
function getDurationPadding(isFlac: boolean) {
|
||||
|
||||
Reference in New Issue
Block a user