Files
QZMusic_PC-pre/amll-local/packages/react/src/lyric-player.tsx

386 lines
12 KiB
TypeScript
Raw Normal View History

import type {
LyricLine,
LyricLineMouseEvent,
LyricPlayerBase,
OptimizeLyricOptions,
spring,
} from "@applemusic-like-lyrics/core";
import {
LyricPlayer as DefaultLyricPlayer,
MaskObsceneWordsMode,
} from "@applemusic-like-lyrics/core";
import {
type ForwardRefExoticComponent,
forwardRef,
type HTMLProps,
type RefAttributes,
useEffect,
useImperativeHandle,
useLayoutEffect,
useRef,
useState,
} from "react";
import { createPortal } from "react-dom";
/**
*
*/
export interface LyricPlayerProps {
/**
* `false`
*
* `update`
*/
disabled?: boolean;
/**
* `true`
*/
playing?: boolean;
/**
* `undefined` `center`
*
* - `top`
* - `bottom`
* - `center`
*/
alignAnchor?: "top" | "bottom" | "center";
/**
* `undefined`
* `0.5`
*
* `[0.0-1.0]`
*/
alignPosition?: number;
/**
* 使
*
*
*
* 退 `transition`
*/
enableSpring?: boolean;
/**
* `true`
*/
enableBlur?: boolean;
/**
* 使
*
*
*
* 退 `transition`
*/
enableScale?: boolean;
/**
*
*/
hidePassedLines?: boolean;
/**
* `MaskObsceneWordsMode.Disabled`
*/
maskObsceneWordsMode?: MaskObsceneWordsMode;
/**
* 使 `*`
*/
maskObsceneWordChar?: string;
/**
*
*/
optimizeOptions?: OptimizeLyricOptions;
/**
*
*/
lyricLines?: LyricLine[];
/**
* ****
*
*/
currentTime?: number;
isSeeking?: boolean;
/**
* 0.5
*
* Apple Music for Android 1
*
* Apple Music for iPad 0.5
*
* 0 `0.0001` ** 0**
*/
wordFadeWidth?: number;
/**
*
*
* @param params
*/
linePosXSpringParams?: Partial<spring.SpringParams>;
/**
*
*
* @param params
*/
linePosYSpringParams?: Partial<spring.SpringParams>;
/**
*
*
* @param params
*/
lineScaleSpringParams?: Partial<spring.SpringParams>;
/**
*
*
*
*/
bottomLine?: Parameters<typeof createPortal>[0];
/**
*
*/
lyricPlayer?: {
new (
...args: ConstructorParameters<typeof LyricPlayerBase>
): LyricPlayerBase;
};
/**
*
* @param line 访
*/
onLyricLineClick?: (line: LyricLineMouseEvent) => void;
/**
*
* @param line 访
*/
onLyricLineContextMenu?: (line: LyricLineMouseEvent) => void;
}
/**
*
*/
export interface LyricPlayerRef {
/**
*
*/
lyricPlayer?: LyricPlayerBase;
/**
* DIV
*/
wrapperEl: HTMLDivElement | null;
}
/**
*
*
* Apple Music for iPad
*/
export const LyricPlayer: ForwardRefExoticComponent<
Omit<HTMLProps<HTMLDivElement> & LyricPlayerProps, "ref"> &
RefAttributes<LyricPlayerRef>
> = forwardRef<LyricPlayerRef, HTMLProps<HTMLDivElement> & LyricPlayerProps>(
(
{
disabled,
playing,
alignAnchor,
alignPosition,
enableSpring,
enableBlur,
enableScale,
maskObsceneWordsMode,
maskObsceneWordChar,
hidePassedLines,
optimizeOptions,
lyricLines,
currentTime,
isSeeking,
wordFadeWidth,
linePosXSpringParams,
linePosYSpringParams,
lineScaleSpringParams,
bottomLine,
lyricPlayer,
onLyricLineClick,
onLyricLineContextMenu,
...props
},
ref,
) => {
// const corePlayerRef = useRef<LyricPlayerBase>();
const [corePlayer, setCorePlayer] = useState<LyricPlayerBase>();
const wrapperRef = useRef<HTMLDivElement>(null);
const currentTimeRef = useRef(currentTime);
useLayoutEffect(() => {
const newPlayer = new (lyricPlayer ?? DefaultLyricPlayer)();
setCorePlayer(newPlayer);
wrapperRef.current?.appendChild(newPlayer.getElement());
return () => {
newPlayer?.dispose();
setCorePlayer(undefined);
};
}, [lyricPlayer]);
useLayoutEffect(() => {
if (optimizeOptions !== undefined) {
corePlayer?.setOptimizeOptions(optimizeOptions);
}
if (lyricLines !== undefined) {
corePlayer?.setLyricLines(lyricLines, currentTimeRef.current);
if (currentTimeRef.current !== undefined) {
corePlayer?.setCurrentTime(currentTimeRef.current, true);
}
corePlayer?.update();
} else {
corePlayer?.setLyricLines([]);
corePlayer?.update();
}
}, [corePlayer, lyricLines, optimizeOptions]);
useEffect(() => {
if (!disabled) {
let canceled = false;
let lastTime = -1;
const onFrame = (time: number) => {
if (canceled) return;
if (lastTime === -1) {
lastTime = time;
}
corePlayer?.update(time - lastTime);
lastTime = time;
requestAnimationFrame(onFrame);
};
corePlayer?.calcLayout();
requestAnimationFrame(onFrame);
return () => {
canceled = true;
};
}
return;
}, [corePlayer, disabled]);
useEffect(() => {
if (playing !== undefined) {
if (playing) {
corePlayer?.resume();
} else {
corePlayer?.pause();
}
} else corePlayer?.resume();
}, [corePlayer, playing]);
useEffect(() => {
if (alignAnchor !== undefined) corePlayer?.setAlignAnchor(alignAnchor);
}, [corePlayer, alignAnchor]);
useEffect(() => {
if (hidePassedLines !== undefined)
corePlayer?.setHidePassedLines(hidePassedLines);
}, [corePlayer, hidePassedLines]);
useEffect(() => {
if (alignPosition !== undefined)
corePlayer?.setAlignPosition(alignPosition);
}, [corePlayer, alignPosition]);
useEffect(() => {
if (enableSpring !== undefined) corePlayer?.setEnableSpring(enableSpring);
else corePlayer?.setEnableSpring(true);
}, [corePlayer, enableSpring]);
useEffect(() => {
if (enableScale !== undefined) corePlayer?.setEnableScale(enableScale);
else corePlayer?.setEnableScale(true);
}, [corePlayer, enableScale]);
useEffect(() => {
corePlayer?.setEnableBlur(enableBlur ?? true);
}, [corePlayer, enableBlur]);
useLayoutEffect(() => {
if (currentTime !== undefined) {
corePlayer?.setCurrentTime(currentTime, isSeeking);
currentTimeRef.current = currentTime;
} else {
corePlayer?.setCurrentTime(0);
currentTimeRef.current = 0;
}
}, [corePlayer, currentTime, isSeeking]);
useEffect(() => {
corePlayer?.setIsSeeking(!!isSeeking);
}, [corePlayer, isSeeking]);
useEffect(() => {
corePlayer?.setWordFadeWidth(wordFadeWidth);
}, [corePlayer, wordFadeWidth]);
useEffect(() => {
if (linePosXSpringParams !== undefined)
corePlayer?.setLinePosXSpringParams(linePosXSpringParams);
}, [corePlayer, linePosXSpringParams]);
useEffect(() => {
if (linePosYSpringParams !== undefined)
corePlayer?.setLinePosYSpringParams(linePosYSpringParams);
}, [corePlayer, linePosYSpringParams]);
useEffect(() => {
if (lineScaleSpringParams !== undefined)
corePlayer?.setLineScaleSpringParams(lineScaleSpringParams);
}, [corePlayer, lineScaleSpringParams]);
useEffect(() => {
if (maskObsceneWordsMode !== undefined) {
corePlayer?.setMaskObsceneWords(maskObsceneWordsMode);
} else {
corePlayer?.setMaskObsceneWords(MaskObsceneWordsMode.Disabled);
}
}, [corePlayer, maskObsceneWordsMode]);
useEffect(() => {
if (maskObsceneWordChar !== undefined) {
corePlayer?.setMaskObsceneWordChar(maskObsceneWordChar);
}
}, [corePlayer, maskObsceneWordChar]);
useEffect(() => {
if (onLyricLineClick) {
const handler = (e: Event) =>
onLyricLineClick(e as LyricLineMouseEvent);
corePlayer?.addEventListener("line-click", handler);
return () => corePlayer?.removeEventListener("line-click", handler);
}
return;
}, [corePlayer, onLyricLineClick]);
useEffect(() => {
if (onLyricLineContextMenu) {
const handler = (e: Event) =>
onLyricLineContextMenu(e as LyricLineMouseEvent);
corePlayer?.addEventListener("line-contextmenu", handler);
return () =>
corePlayer?.removeEventListener("line-contextmenu", handler);
}
return;
}, [corePlayer, onLyricLineContextMenu]);
useImperativeHandle(
ref,
() => ({
wrapperEl: wrapperRef.current,
lyricPlayer: corePlayer,
}),
[corePlayer],
);
return (
<>
<div {...props} ref={wrapperRef} />
{corePlayer?.getBottomLineElement() && bottomLine
? createPortal(bottomLine, corePlayer?.getBottomLineElement())
: null}
</>
);
},
);