lrc-maker
Version:
歌词滚动姬|可能是你所能见到的最好用的歌词制作工具
214 lines • 8.47 kB
JavaScript
import { audioRef, audioStatePubSub, currentTimePubSub } from "../utils/audiomodule.js";
import { isKeyboardElement } from "../utils/is-keyboard-element.js";
import { appContext } from "./app.context.js";
import { LrcAudio } from "./audio.js";
import { LoadAudio, nec } from "./loadaudio.js";
import { toastPubSub } from "./toast.js";
const { useCallback, useContext, useEffect, useReducer, useRef } = React;
const accept = ["audio/*", ".ncm", ".qmcflac", ".qmc0", ".qmc1", ".qmc2", ".qmc3", "qmcogg"].join(", ");
export const Footer = () => {
const { prefState, lang } = useContext(appContext, 1 | 2);
const [audioSrc, setAudioSrc] = useReducer((oldSrc, newSrc) => {
URL.revokeObjectURL(oldSrc);
return newSrc;
}, undefined, () => {
let src = sessionStorage.getItem("audio-src");
if (src === null && location.search && URLSearchParams) {
const searchParams = new URLSearchParams(location.search);
const url = searchParams.get("url");
if (url !== null) {
return url;
}
const text = searchParams.get("text") || searchParams.get("title") || "";
const result = /https?:\/\/\S+/.exec(text);
src = result && nec(result[0]);
}
return src;
});
useEffect(() => {
document.addEventListener("keydown", (ev) => {
const { code, key, target } = ev;
const codeOrKey = code || key;
if (isKeyboardElement(target)) {
return;
}
if (!audioRef.src) {
return;
}
if (ev.metaKey === true || ev.ctrlKey === true) {
if (["ArrowUp", "KeyJ", "Up", "J", "j"].includes(codeOrKey)) {
ev.preventDefault();
const rate = audioRef.playbackRate;
const newRate = Math.exp(Math.min(Math.log(rate) + 0.2, 1));
audioRef.playbackRate = newRate;
}
else if (["ArrowDown", "KeyK", "Down", "K", "k"].includes(codeOrKey)) {
ev.preventDefault();
const rate = audioRef.playbackRate;
const newRate = Math.exp(Math.max(Math.log(rate) - 0.2, -1));
audioRef.playbackRate = newRate;
}
else if (codeOrKey === "Enter") {
ev.preventDefault();
audioRef.toggle();
}
}
else {
if (["ArrowLeft", "KeyA", "KeyH", "Left", "A", "a", "H", "h"].includes(codeOrKey)) {
ev.preventDefault();
audioRef.step(ev, -5);
}
else if (["ArrowRight", "KeyD", "KeyL", "Right", "D", "d", "L", "l"].includes(codeOrKey)) {
ev.preventDefault();
audioRef.step(ev, 5);
}
else if (code === "KeyR" || key === "R" || key === "r") {
ev.preventDefault();
audioRef.playbackRate = 1;
}
}
});
}, []);
useEffect(() => {
document.documentElement.addEventListener("drop", (ev) => {
const file = ev.dataTransfer.files[0];
receiveFile(file, setAudioSrc);
});
}, []);
const onAudioInputChange = useCallback((ev) => {
const file = ev.target.files[0];
receiveFile(file, setAudioSrc);
}, []);
const rafId = useRef(0);
const onAudioLoadedMetadata = useCallback(() => {
cancelAnimationFrame(rafId.current);
audioStatePubSub.pub({
type: 1,
payload: audioRef.duration,
});
toastPubSub.pub({
type: "success",
text: lang.notify.audioLoaded,
});
}, [lang]);
const syncCurrentTime = useCallback(() => {
currentTimePubSub.pub(audioRef.currentTime);
rafId.current = requestAnimationFrame(syncCurrentTime);
}, []);
const onAudioPlay = useCallback(() => {
rafId.current = requestAnimationFrame(syncCurrentTime);
audioStatePubSub.pub({
type: 0,
payload: false,
});
}, [syncCurrentTime]);
const onAudioPause = useCallback(() => {
cancelAnimationFrame(rafId.current);
audioStatePubSub.pub({
type: 0,
payload: true,
});
}, []);
const onAudioEnded = useCallback(() => {
cancelAnimationFrame(rafId.current);
audioStatePubSub.pub({
type: 0,
payload: true,
});
}, []);
const onAudioTimeUpdate = useCallback(() => {
if (audioRef.paused) {
currentTimePubSub.pub(audioRef.currentTime);
}
}, []);
const onAudioRateChange = useCallback(() => {
audioStatePubSub.pub({
type: 2,
payload: audioRef.playbackRate,
});
}, []);
const onAudioError = useCallback((ev) => {
const audio = ev.target;
const error = audio.error;
const message = lang.audio.error[error.code] || error.message || lang.audio.error[0];
toastPubSub.pub({
type: "warning",
text: message,
});
}, [lang]);
return (React.createElement("footer", { className: "app-footer" },
React.createElement("input", { id: "audio-input", type: "file", accept: accept, hidden: true, onChange: onAudioInputChange }),
React.createElement(LoadAudio, { setAudioSrc: setAudioSrc, lang: lang }),
React.createElement("audio", { ref: audioRef, src: audioSrc, controls: prefState.builtInAudio, hidden: !prefState.builtInAudio, onLoadedMetadata: onAudioLoadedMetadata, onPlay: onAudioPlay, onPause: onAudioPause, onEnded: onAudioEnded, onTimeUpdate: onAudioTimeUpdate, onRateChange: onAudioRateChange, onError: onAudioError }),
prefState.builtInAudio || React.createElement(LrcAudio, { lang: lang })));
};
const receiveFile = (file, setAudioSrc) => {
sessionStorage.removeItem("audio-src");
if (file) {
if (file.type.startsWith("audio/")) {
setAudioSrc(URL.createObjectURL(file));
return;
}
if (file.name.endsWith(".ncm")) {
const worker = new Worker("./ncmc-worker.js");
worker.addEventListener("message", (ev) => {
if (ev.data.type === "success") {
const dataArray = ev.data.payload;
const musicFile = new Blob([dataArray], {
type: detectMimeType(dataArray),
});
setAudioSrc(URL.createObjectURL(musicFile));
}
if (ev.data.type === "error") {
toastPubSub.pub({
type: "warning",
text: ev.data.payload,
});
}
}, { once: true });
worker.addEventListener("error", (ev) => {
toastPubSub.pub({
type: "warning",
text: ev.message,
});
worker.terminate();
}, { once: true });
worker.postMessage(file);
return;
}
if (/\.qmc(?:flac|0|1|2|3)$/.test(file.name)) {
const worker = new Worker("./qmc-worker.js");
worker.addEventListener("message", (ev) => {
if (ev.data.type === "success") {
const dataArray = ev.data.payload;
const musicFile = new Blob([dataArray], {
type: detectMimeType(dataArray),
});
setAudioSrc(URL.createObjectURL(musicFile));
}
}, { once: true });
worker.postMessage(file);
return;
}
}
};
const detectMimeType = (dataArray) => {
const magicNumber = new DataView(dataArray.buffer).getUint32(0, false);
switch (magicNumber) {
case 1716281667:
return "audio/flac";
case 1332176723:
return "audio/ogg";
case 1380533830:
case 1463899717:
return "audio/wav";
default:
return "audio/mpeg";
}
};
document.addEventListener("visibilitychange", () => {
if (!audioRef.paused) {
audioRef.toggle();
}
});
//# sourceMappingURL=footer.js.map