|
@@ -9,6 +9,33 @@ export enum PRELOADER_STATUS {
|
|
|
ABORT = "abort",
|
|
|
}
|
|
|
|
|
|
+export const getMediaType = (url: string) => {
|
|
|
+ const MEDIA_TYPES: { [key: string]: string } = {
|
|
|
+ mp4: "video/mp4",
|
|
|
+ webm: "video/webm",
|
|
|
+ ogg: "video/ogg",
|
|
|
+ mp3: "audio/mp3",
|
|
|
+ wav: "audio/wav",
|
|
|
+ };
|
|
|
+ const urlExtension = url.split(".").pop()?.toLowerCase() || "";
|
|
|
+
|
|
|
+ if (MEDIA_TYPES.hasOwnProperty(urlExtension)) {
|
|
|
+ return MEDIA_TYPES[urlExtension];
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const parseUrlName = (url: string) => {
|
|
|
+ const regex = /\/([^/]+)\.\w+$/;
|
|
|
+ const match = url.match(regex);
|
|
|
+ if (match && match.length >= 2) {
|
|
|
+ return match[1].split(".")[0];
|
|
|
+ } else {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
export function usePreloader(params: {
|
|
|
list: string[];
|
|
|
/**
|
|
@@ -17,16 +44,22 @@ export function usePreloader(params: {
|
|
|
decimals?: number;
|
|
|
success?: Function;
|
|
|
error?: Function;
|
|
|
+ /**
|
|
|
+ * 尝试重连次数
|
|
|
+ * @default 3
|
|
|
+ */
|
|
|
+ retry?: number;
|
|
|
}) {
|
|
|
const decimals = params.decimals ? params.decimals * 10 : 1;
|
|
|
const fetchControllerStack = useRef<AbortController[]>([]);
|
|
|
const _list = useRef(params.list);
|
|
|
+ const _retry = params.retry ?? 3;
|
|
|
|
|
|
/**
|
|
|
* 总字节长度
|
|
|
*/
|
|
|
const totalLength = useRef(0);
|
|
|
- const fileSizeStack = useRef<number[]>([]);
|
|
|
+ const fileSizeStack = useRef<Map<string, number>>(new Map());
|
|
|
/**
|
|
|
* 已接收字节长度
|
|
|
*/
|
|
@@ -38,29 +71,49 @@ export function usePreloader(params: {
|
|
|
const [status, setStatus] = useState<PRELOADER_STATUS>(
|
|
|
PRELOADER_STATUS.WAITING
|
|
|
);
|
|
|
+ /**
|
|
|
+ * 缓存的媒体资源本地url
|
|
|
+ */
|
|
|
+ const mediaUrlMap = useRef(new Map<string, string>());
|
|
|
|
|
|
const setThrottlePercent = useCallback(
|
|
|
throttle((val: string) => setPercent(val), 200),
|
|
|
[]
|
|
|
);
|
|
|
|
|
|
- const handlePreload = (url: string) => {
|
|
|
+ const calculateSum = (map: Map<string, number>) => {
|
|
|
+ let sum = 0;
|
|
|
+ for (const value of map.values()) {
|
|
|
+ sum += value;
|
|
|
+ }
|
|
|
+ return sum;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handlePreload = (url: string, retry = _retry, loadedLength = 0) => {
|
|
|
const controller = new AbortController();
|
|
|
const signal = controller.signal;
|
|
|
+ const mediaType = getMediaType(url);
|
|
|
+ let loadingLength = 0;
|
|
|
|
|
|
return new Promise((res, rej) => {
|
|
|
fetch(url, { signal })
|
|
|
.then((response) => {
|
|
|
const contentLength = Number(response.headers.get("Content-Length"));
|
|
|
- fileSizeStack.current.push(contentLength);
|
|
|
+
|
|
|
+ fileSizeStack.current.set(url, contentLength);
|
|
|
|
|
|
if (response.ok) {
|
|
|
return response.body;
|
|
|
}
|
|
|
})
|
|
|
.then(async (body) => {
|
|
|
- if (!body) return;
|
|
|
+ if (!body) {
|
|
|
+ rej(`request.body is not find`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
+ let diffed = false;
|
|
|
+ const chunks: Uint8Array[] = [];
|
|
|
const reader = body.getReader();
|
|
|
const loop = true;
|
|
|
|
|
@@ -69,24 +122,62 @@ export function usePreloader(params: {
|
|
|
|
|
|
if (done) break;
|
|
|
|
|
|
- downloadLength.current += value.length;
|
|
|
+ loadingLength += value.length;
|
|
|
+ if (loadingLength >= loadedLength) {
|
|
|
+ if (!diffed && loadedLength) {
|
|
|
+ const diff = loadingLength - loadedLength;
|
|
|
+ console.log(diff);
|
|
|
+ downloadLength.current += diff;
|
|
|
+ diffed = true;
|
|
|
+ }
|
|
|
|
|
|
- if (fileSizeStack.current.length === _list.current.length) {
|
|
|
- const mediaSize = fileSizeStack.current.reduce(
|
|
|
- (pre, cur) => pre + cur
|
|
|
- );
|
|
|
+ downloadLength.current += value.length;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mediaType) {
|
|
|
+ chunks.push(value);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (fileSizeStack.current.size === _list.current.length) {
|
|
|
+ const sum = calculateSum(fileSizeStack.current);
|
|
|
const percent = Math.round(
|
|
|
- (downloadLength.current / mediaSize) * 100 * decimals
|
|
|
+ (downloadLength.current / sum) * 100 * decimals
|
|
|
);
|
|
|
|
|
|
setThrottlePercent((percent / decimals).toFixed(params.decimals));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (mediaType) {
|
|
|
+ const blob = new Blob(chunks, { type: mediaType });
|
|
|
+ mediaUrlMap.current.set(
|
|
|
+ parseUrlName(url),
|
|
|
+ URL.createObjectURL(blob)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
res(true);
|
|
|
})
|
|
|
.catch((err) => {
|
|
|
- rej(err);
|
|
|
+ if (err.name === "AbortError") {
|
|
|
+ rej(err);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (retry) {
|
|
|
+ console.log("尝试重新预加载");
|
|
|
+ setTimeout(() => {
|
|
|
+ handlePreload(url, retry - 1, loadingLength)
|
|
|
+ .then(res)
|
|
|
+ .catch(rej);
|
|
|
+ }, 2000);
|
|
|
+ } else {
|
|
|
+ if (mediaType) {
|
|
|
+ mediaUrlMap.current.set(parseUrlName(url), url);
|
|
|
+ }
|
|
|
+
|
|
|
+ rej(err);
|
|
|
+ }
|
|
|
});
|
|
|
|
|
|
fetchControllerStack.current.push(controller);
|
|
@@ -98,14 +189,15 @@ export function usePreloader(params: {
|
|
|
|
|
|
totalLength.current = 0;
|
|
|
downloadLength.current = 0;
|
|
|
- fileSizeStack.current = [];
|
|
|
+ fileSizeStack.current.clear();
|
|
|
+ mediaUrlMap.current.clear();
|
|
|
setThrottlePercent("0");
|
|
|
};
|
|
|
|
|
|
const start = () => {
|
|
|
if (status === PRELOADER_STATUS.LOADING) return;
|
|
|
|
|
|
- if ([PRELOADER_STATUS.ERROR, PRELOADER_STATUS.ABORT].includes(status)) {
|
|
|
+ if (status !== PRELOADER_STATUS.WAITING) {
|
|
|
resetParams();
|
|
|
}
|
|
|
|
|
@@ -136,9 +228,10 @@ export function usePreloader(params: {
|
|
|
|
|
|
return {
|
|
|
status,
|
|
|
- totalLength,
|
|
|
- downloadLength,
|
|
|
percent,
|
|
|
+ totalLength: totalLength.current,
|
|
|
+ downloadLength: downloadLength.current,
|
|
|
+ mediaUrlMap: mediaUrlMap.current,
|
|
|
start,
|
|
|
abort,
|
|
|
};
|