|
@@ -0,0 +1,145 @@
|
|
|
+import { throttle } from "lodash";
|
|
|
+import { useCallback, useRef, useState } from "react";
|
|
|
+
|
|
|
+export enum PRELOADER_STATUS {
|
|
|
+ ERROR = "error",
|
|
|
+ LOADING = "loading",
|
|
|
+ WAITING = "waiting",
|
|
|
+ DONE = "done",
|
|
|
+ ABORT = "abort",
|
|
|
+}
|
|
|
+
|
|
|
+export function usePreloader(params: {
|
|
|
+ list: string[];
|
|
|
+ /**
|
|
|
+ * 百分比保留几位小数
|
|
|
+ */
|
|
|
+ decimals?: number;
|
|
|
+ success?: Function;
|
|
|
+ error?: Function;
|
|
|
+}) {
|
|
|
+ const decimals = params.decimals ? params.decimals * 10 : 1;
|
|
|
+ const fetchControllerStack = useRef<AbortController[]>([]);
|
|
|
+ const _list = useRef(params.list);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 总字节长度
|
|
|
+ */
|
|
|
+ const totalLength = useRef(0);
|
|
|
+ const fileSizeStack = useRef<number[]>([]);
|
|
|
+ /**
|
|
|
+ * 已接收字节长度
|
|
|
+ */
|
|
|
+ const downloadLength = useRef(0);
|
|
|
+ /**
|
|
|
+ * 进度
|
|
|
+ */
|
|
|
+ const [percent, setPercent] = useState("0");
|
|
|
+ const [status, setStatus] = useState<PRELOADER_STATUS>(
|
|
|
+ PRELOADER_STATUS.WAITING
|
|
|
+ );
|
|
|
+
|
|
|
+ const setThrottlePercent = useCallback(
|
|
|
+ throttle((val: string) => setPercent(val), 200),
|
|
|
+ []
|
|
|
+ );
|
|
|
+
|
|
|
+ const handlePreload = (url: string) => {
|
|
|
+ const controller = new AbortController();
|
|
|
+ const signal = controller.signal;
|
|
|
+
|
|
|
+ return new Promise((res, rej) => {
|
|
|
+ fetch(url, { signal })
|
|
|
+ .then((response) => {
|
|
|
+ const contentLength = Number(response.headers.get("Content-Length"));
|
|
|
+ fileSizeStack.current.push(contentLength);
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ return response.body;
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .then(async (body) => {
|
|
|
+ if (!body) return;
|
|
|
+
|
|
|
+ const reader = body.getReader();
|
|
|
+ const loop = true;
|
|
|
+
|
|
|
+ while (loop) {
|
|
|
+ const { value, done } = await reader.read();
|
|
|
+
|
|
|
+ if (done) break;
|
|
|
+
|
|
|
+ downloadLength.current += value.length;
|
|
|
+
|
|
|
+ if (fileSizeStack.current.length === _list.current.length) {
|
|
|
+ const mediaSize = fileSizeStack.current.reduce(
|
|
|
+ (pre, cur) => pre + cur
|
|
|
+ );
|
|
|
+ const percent = Math.round(
|
|
|
+ (downloadLength.current / mediaSize) * 100 * decimals
|
|
|
+ );
|
|
|
+
|
|
|
+ setThrottlePercent((percent / decimals).toFixed(params.decimals));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ res(true);
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ rej(err);
|
|
|
+ });
|
|
|
+
|
|
|
+ fetchControllerStack.current.push(controller);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const resetParams = () => {
|
|
|
+ abort();
|
|
|
+
|
|
|
+ totalLength.current = 0;
|
|
|
+ downloadLength.current = 0;
|
|
|
+ fileSizeStack.current = [];
|
|
|
+ setThrottlePercent("0");
|
|
|
+ };
|
|
|
+
|
|
|
+ const start = () => {
|
|
|
+ if (status === PRELOADER_STATUS.LOADING) return;
|
|
|
+
|
|
|
+ if ([PRELOADER_STATUS.ERROR, PRELOADER_STATUS.ABORT].includes(status)) {
|
|
|
+ resetParams();
|
|
|
+ }
|
|
|
+
|
|
|
+ setStatus(PRELOADER_STATUS.LOADING);
|
|
|
+
|
|
|
+ Promise.all(_list.current.map((url) => handlePreload(url)))
|
|
|
+ .then(() => {
|
|
|
+ params.success?.();
|
|
|
+ setStatus(PRELOADER_STATUS.DONE);
|
|
|
+ })
|
|
|
+ .catch((err: Error) => {
|
|
|
+ if (err.name === "AbortError") return;
|
|
|
+
|
|
|
+ abort();
|
|
|
+ params.error?.(err);
|
|
|
+ setStatus(PRELOADER_STATUS.ERROR);
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const abort = () => {
|
|
|
+ fetchControllerStack.current.forEach((controller) => {
|
|
|
+ controller.abort();
|
|
|
+ });
|
|
|
+ fetchControllerStack.current = [];
|
|
|
+
|
|
|
+ setStatus(PRELOADER_STATUS.ABORT);
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ status,
|
|
|
+ totalLength,
|
|
|
+ downloadLength,
|
|
|
+ percent,
|
|
|
+ start,
|
|
|
+ abort,
|
|
|
+ };
|
|
|
+}
|