Explorar o código

feat[hooks]: usePreloader本地存储媒体

chenlei hai 1 ano
pai
achega
5733cb64ed
Modificáronse 3 ficheiros con 128 adicións e 16 borrados
  1. 19 1
      packages/docs/docs/utils/hooks.md
  2. 108 15
      packages/hooks/src/usePreloader.ts
  3. 1 0
      scripts/publish.js

+ 19 - 1
packages/docs/docs/utils/hooks.md

@@ -40,6 +40,10 @@ usePreloader({
    */
   totalLength: number;
   /**
+   * 本地媒体资源url
+   */
+  mediaUrlMap: Map<string, string>;
+  /**
    * 开始预加载
    */
   start: () => void;
@@ -58,12 +62,13 @@ import { usePreloader, PRELOADER_STATUS } from "@dage/hooks";
 import { Progress, Space, Button } from "antd";
 
 export default () => {
-  const { percent, status, start, abort } = usePreloader({
+  const { percent, status, start, abort, mediaUrlMap } = usePreloader({
     list: [
       "https://houseoss.4dkankan.com/project/yzdyh-dadu/media/end.bad6e656.mp4",
       "https://houseoss.4dkankan.com/project/yzdyh-dadu/media/epilogue.ff00412e.mp4",
     ],
   });
+  const isDone = status === PRELOADER_STATUS.DONE;
 
   return (
     <>
@@ -80,6 +85,19 @@ export default () => {
           开始预加载
         </Button>
       )}
+
+      {isDone && (
+        <video
+          controls
+          src={mediaUrlMap.get("end")}
+          style={{
+            display: "block",
+            marginTop: "20px",
+            width: "667px",
+            height: "375px",
+          }}
+        />
+      )}
     </>
   );
 };

+ 108 - 15
packages/hooks/src/usePreloader.ts

@@ -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,
   };

+ 1 - 0
scripts/publish.js

@@ -7,6 +7,7 @@ const packages = [
   "pc-components",
   "utils",
   "krpano",
+  "hooks",
 ];
 
 packages.forEach((name) => {