Sfoglia il codice sorgente

为安卓浏览器做专门优化,目前未解决问题做特殊提示,优化成为正式用户需要填写的字段。

wilhelm wong 2 mesi fa
parent
commit
e31bc45996
3 ha cambiato i file con 253 aggiunte e 80 eliminazioni
  1. 1 1
      dev-dist/sw.js
  2. 229 39
      src/components/VideoProcessor.vue
  3. 23 40
      src/views/Account.vue

+ 1 - 1
dev-dist/sw.js

@@ -79,7 +79,7 @@ define(['./workbox-f2cb1a81'], (function (workbox) { 'use strict';
    */
   workbox.precacheAndRoute([{
     "url": "index.html",
-    "revision": "0.ub7ebg6m7o8"
+    "revision": "0.o0r0lekt7ho"
   }], {});
   workbox.cleanupOutdatedCaches();
   workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

+ 229 - 39
src/components/VideoProcessor.vue

@@ -199,6 +199,8 @@ const isFullscreen = ref(false);
 const showControls = ref(true);
 const showVolumeSlider = ref(true); // 桌面端显示音量滑块,移动端通过CSS隐藏
 let controlsTimer: ReturnType<typeof setTimeout> | null = null;
+let errorDebounceTimer: ReturnType<typeof setTimeout> | null = null; // 错误防抖定时器
+const isVideoLoading = ref(false); // 视频是否正在加载
 
 // 计算属性
 const isVideoMode = computed(() => !!props.m3u8Url);
@@ -336,6 +338,13 @@ const processVideo = async (url: string): Promise<void> => {
         if (isQuarkBrowser() && !isIOSQuarkBrowser()) {
           // Android 夸克使用 Apple 格式
           mimeType = "application/vnd.apple.mpegurl";
+          console.log("夸克浏览器使用 Apple HLS 格式");
+        }
+        
+        if (isQQBrowser()) {
+          // QQ 浏览器(X5内核)必须使用 Apple 格式
+          mimeType = "application/x-mpegURL";
+          console.log("QQ浏览器使用 Apple HLS 格式(X5内核要求)");
         }
         
         // iOS 夸克浏览器:使用 Data URL(iOS 不支持 Blob URL + HLS.js)
@@ -343,6 +352,7 @@ const processVideo = async (url: string): Promise<void> => {
           try {
             const base64 = btoa(unescape(encodeURIComponent(playlist)));
             processedVideoUrl.value = `data:${mimeType};base64,${base64}`;
+            console.log("iOS 夸克浏览器使用 Data URL");
           } catch (e) {
             console.error("Data URL 生成失败:", e);
             error.value = "抱歉,iOS 夸克浏览器暂不支持播放此视频,建议使用 Safari 打开";
@@ -350,7 +360,34 @@ const processVideo = async (url: string): Promise<void> => {
             loading.value = false;
             return;
           }
-        } else {
+        } 
+        // else if (isQQBrowser()) {
+        //   // QQ浏览器:使用正确的MIME类型
+        //   console.log("检测到QQ浏览器,使用HLS.js兼容模式");
+          
+        //   try {
+        //     // 使用正确的HLS MIME类型
+        //     const blob = new Blob([playlist], { type: "application/vnd.apple.mpegurl" });
+        //     processedVideoUrl.value = URL.createObjectURL(blob);
+        //     console.log("QQ浏览器使用Blob URL,MIME类型:", "application/vnd.apple.mpegurl");
+        //   } catch (e) {
+        //     console.error("QQ浏览器Blob URL生成失败,尝试Data URL:", e);
+            
+        //     // Blob失败则尝试Data URL
+        //     try {
+        //       const base64 = btoa(unescape(encodeURIComponent(playlist)));
+        //       processedVideoUrl.value = `data:application/vnd.apple.mpegurl;base64,${base64}`;
+        //       console.log("QQ浏览器降级使用Data URL");
+        //     } catch (e2) {
+        //       console.error("QQ浏览器Data URL生成失败:", e2);
+        //       error.value = "QQ浏览器视频处理失败,建议使用Chrome浏览器";
+        //       emit("error", error.value);
+        //       loading.value = false;
+        //       return;
+        //     }
+        //   }
+        // } 
+        else {
           // 其他浏览器使用 Blob URL
           const blob = new Blob([playlist], { type: mimeType });
           processedVideoUrl.value = URL.createObjectURL(blob);
@@ -431,6 +468,18 @@ const isIOSQuarkBrowser = (): boolean => {
   return isQuarkBrowser() && (/iPad|iPhone|iPod/.test(navigator.userAgent));
 };
 
+// 检测是否为 QQ 浏览器
+const isQQBrowser = (): boolean => {
+  const ua = navigator.userAgent.toLowerCase();
+  return ua.includes('mqqbrowser') || ua.includes('qq/');
+};
+
+// 检测是否为 UC 浏览器
+const isUCBrowser = (): boolean => {
+  const ua = navigator.userAgent.toLowerCase();
+  return ua.includes('ucbrowser') || ua.includes('ubrowser') || ua.includes('ucweb');
+};
+
 // 初始化视频播放器
 const initVideoPlayer = async (): Promise<void> => {
   if (!videoElement.value || !processedVideoUrl.value) {
@@ -440,13 +489,29 @@ const initVideoPlayer = async (): Promise<void> => {
   const video = videoElement.value;
   destroyHls();
 
+  // 设置加载状态
+  isVideoLoading.value = true;
+  error.value = "";
+  
+  // 清除错误防抖定时器
+  if (errorDebounceTimer) {
+    clearTimeout(errorDebounceTimer);
+    errorDebounceTimer = null;
+  }
+
+  // 清除之前的src,避免缓存问题
+  if (video.src) {
+    video.removeAttribute('src');
+    video.load();
+  }
+
   // iOS 夸克浏览器特殊处理(最优先)
   // iOS 不支持 HLS.js,且夸克的自定义播放器对 Blob/Data URL 支持有限
   // 直接使用原生播放器,让 iOS 处理
   if (isIOSQuarkBrowser()) {
     console.log("检测到 iOS 夸克浏览器,使用原生播放器");
-    video.crossOrigin = "anonymous";
     video.src = processedVideoUrl.value;
+    video.load(); // 强制重新加载
     return;
   }
 
@@ -460,8 +525,34 @@ const initVideoPlayer = async (): Promise<void> => {
       initHlsPlayer();
     } else {
       // 降级到原生播放
-      video.crossOrigin = "anonymous";
+      console.log("HLS.js 不支持,使用原生播放器");
       video.src = processedVideoUrl.value;
+      video.load(); // 强制重新加载
+    }
+    return;
+  }
+
+  // QQ 浏览器不支持提示
+  if (isQQBrowser()) {
+    console.log("检测到 QQ 浏览器,不支持加密视频播放");
+    error.value = "QQ浏览器暂不支持播放此类视频\n请使用Chrome、UC或其他浏览器访问";
+    // 不提供重试选项,直接返回
+    return;
+  }
+
+  // UC 浏览器特殊处理
+  if (isUCBrowser()) {
+    console.log("检测到 UC 浏览器");
+    
+    // UC浏览器优先使用HLS.js
+    if (Hls.isSupported()) {
+      console.log("UC浏览器使用HLS.js播放器");
+      initHlsPlayer();
+    } else {
+      // 如果HLS.js不支持,使用原生播放器
+      console.log("UC浏览器使用原生播放器");
+      video.src = processedVideoUrl.value;
+      video.load();
     }
     return;
   }
@@ -483,26 +574,33 @@ const initVideoPlayer = async (): Promise<void> => {
     // Safari 原生支持 HLS(非加密流)
     if (video.canPlayType("application/vnd.apple.mpegurl")) {
       video.src = processedVideoUrl.value;
+      video.load();
 
-      // Safari 错误处理
-      video.addEventListener("error", (e) => {
-        error.value = "Safari 视频播放失败,尝试使用 HLS.js";
-
-        // 如果原生播放失败,尝试使用 HLS.js
+      // Safari 错误处理 - 使用一次性事件监听器
+      const handleError = () => {
+        console.log("Safari原生播放失败,尝试HLS.js");
+        error.value = "";
         if (Hls.isSupported()) {
           initHlsPlayer();
+        } else {
+          error.value = "Safari 视频播放失败";
         }
-      });
+      };
+      video.addEventListener("error", handleError, { once: true });
 
       return;
     }
   }
 
-  // 非 Safari 或 Safari 原生播放失败时使用 HLS.js
+  // 其他浏览器使用 HLS.js(性能更好)
   if (Hls.isSupported()) {
+    console.log("使用 HLS.js 播放");
     initHlsPlayer();
   } else {
+    // 降级到原生播放器
+    console.log("HLS.js 不支持,使用原生播放器");
     video.src = processedVideoUrl.value;
+    video.load();
   }
 };
 
@@ -521,40 +619,72 @@ const initHlsPlayer = (): void => {
     maxBufferLength: 30,
     maxMaxBufferLength: 60,
     backBufferLength: 10,
+    // 添加更宽松的配置以提高兼容性
+    manifestLoadingTimeOut: 10000,
+    manifestLoadingMaxRetry: 3,
+    levelLoadingTimeOut: 10000,
+    levelLoadingMaxRetry: 3,
   };
 
-  hlsInstance.value = new Hls(hlsConfig);
+  try {
+    hlsInstance.value = new Hls(hlsConfig);
 
-  hlsInstance.value.loadSource(processedVideoUrl.value);
-  hlsInstance.value.attachMedia(video);
+    hlsInstance.value.loadSource(processedVideoUrl.value);
+    hlsInstance.value.attachMedia(video);
 
-  hlsInstance.value.on(Hls.Events.MANIFEST_PARSED, () => {
-    error.value = "";
-  });
-
-  hlsInstance.value.on(Hls.Events.ERROR, (event, data) => {
-    if (data.fatal) {
-      switch (data.type) {
-        case Hls.ErrorTypes.NETWORK_ERROR:
-          if (data.details === "manifestParsingError") {
-            error.value = "视频清单解析失败";
+    hlsInstance.value.on(Hls.Events.MANIFEST_PARSED, () => {
+      console.log("HLS manifest 解析成功");
+      error.value = "";
+    });
+
+    hlsInstance.value.on(Hls.Events.ERROR, (event, data) => {
+      console.error("HLS 错误:", data);
+      
+      if (data.fatal) {
+        switch (data.type) {
+          case Hls.ErrorTypes.NETWORK_ERROR:
+            console.log("网络错误,尝试恢复");
+            if (data.details === "manifestParsingError") {
+              error.value = "视频清单解析失败";
+              hlsInstance.value?.destroy();
+              hlsInstance.value = null;
+            } else {
+              // 尝试重新加载
+              hlsInstance.value?.startLoad();
+            }
+            break;
+          case Hls.ErrorTypes.MEDIA_ERROR:
+            console.log("媒体错误,尝试恢复");
+            hlsInstance.value?.recoverMediaError();
+            break;
+          default:
+            console.log("其他致命错误,销毁 HLS");
+            error.value = "视频加载失败";
             hlsInstance.value?.destroy();
             hlsInstance.value = null;
-          } else {
-            hlsInstance.value?.startLoad();
-          }
-          break;
-        case Hls.ErrorTypes.MEDIA_ERROR:
-          hlsInstance.value?.recoverMediaError();
-          break;
-        default:
-          error.value = "视频加载失败";
-          hlsInstance.value?.destroy();
-          hlsInstance.value = null;
-          break;
+            
+            // 尝试降级到原生播放器
+            setTimeout(() => {
+              if (videoElement.value && processedVideoUrl.value) {
+                console.log("降级到原生播放器");
+                error.value = "";
+                videoElement.value.src = processedVideoUrl.value;
+                videoElement.value.load();
+              }
+            }, 500);
+            break;
+        }
       }
+    });
+  } catch (err) {
+    console.error("初始化 HLS.js 失败:", err);
+    error.value = "播放器初始化失败";
+    // 降级到原生播放器
+    if (videoElement.value && processedVideoUrl.value) {
+      videoElement.value.src = processedVideoUrl.value;
+      videoElement.value.load();
     }
-  });
+  }
 };
 
 // 销毁 HLS 实例
@@ -707,11 +837,30 @@ const handleCoverError = (event: Event): void => {
 
 const handleCoverLoad = (): void => {};
 
-const onVideoLoadStart = (): void => {};
+const onVideoLoadStart = (): void => {
+  console.log("视频开始加载");
+  isVideoLoading.value = true;
+  // 清除之前的错误
+  error.value = "";
+};
 
-const onVideoLoadedData = (): void => {};
+const onVideoLoadedData = (): void => {
+  console.log("视频数据已加载");
+};
 
 const onVideoCanPlay = (): void => {
+  console.log("视频可以播放");
+  
+  // 清除加载状态和错误
+  isVideoLoading.value = false;
+  error.value = "";
+  
+  // 清除错误防抖定时器
+  if (errorDebounceTimer) {
+    clearTimeout(errorDebounceTimer);
+    errorDebounceTimer = null;
+  }
+  
   if (props.autoPlay && videoElement.value) {
     videoElement.value.play().catch(() => {});
   }
@@ -770,6 +919,42 @@ const onVideoError = (event: Event): void => {
   const errorCode = video.error?.code;
   const errorMessage = video.error?.message;
 
+  console.log("视频错误事件:", errorCode, errorMessage, "加载中:", isVideoLoading.value);
+
+  // 清除之前的错误防抖定时器
+  if (errorDebounceTimer) {
+    clearTimeout(errorDebounceTimer);
+    errorDebounceTimer = null;
+  }
+
+  // 如果视频正在加载中,延迟显示错误(给视频更多时间初始化)
+  if (isVideoLoading.value) {
+    console.log("视频正在初始化,延迟错误显示");
+    
+    // 对于夸克浏览器,给更长的初始化时间(从2s提升至3s)
+    const delayTime = isQuarkBrowser() ? 3000 : 1000;
+    
+    errorDebounceTimer = setTimeout(() => {
+      // 再次检查视频状态
+      if (video.readyState >= 2) {
+        // 视频已经准备好,忽略错误
+        console.log("视频已准备好,忽略错误");
+        return;
+      }
+      
+      // 显示错误
+      showVideoError(errorCode, errorMessage);
+    }, delayTime);
+    
+    return;
+  }
+
+  // 如果不是加载中,立即显示错误
+  showVideoError(errorCode, errorMessage);
+};
+
+// 显示视频错误的辅助函数
+const showVideoError = (errorCode?: number, errorMessage?: string): void => {
   let errorText = "视频播放失败";
 
   if (errorCode) {
@@ -795,7 +980,7 @@ const onVideoError = (event: Event): void => {
     }
   }
 
-  console.error("视频播放错误:", errorCode, errorMessage);
+  console.error("显示视频错误:", errorCode, errorMessage);
   error.value = errorText;
   emit("error", error.value);
 
@@ -873,6 +1058,11 @@ onUnmounted(() => {
     clearTimeout(controlsTimer);
   }
   
+  // 清理错误防抖定时器
+  if (errorDebounceTimer) {
+    clearTimeout(errorDebounceTimer);
+  }
+  
   // 清理 Blob URL
   if (processedCoverUrl.value?.startsWith("blob:")) {
     URL.revokeObjectURL(processedCoverUrl.value);

+ 23 - 40
src/views/Account.vue

@@ -35,10 +35,8 @@ const errorMessage = ref("");
 const successMessage = ref("");
 const currentOrderNo = ref("");
 const upgradeForm = ref({
-  name: null,
   password: null,
-  email: undefined,
-  phone: undefined,
+  email: null,
 });
 const isLoading = ref(false);
 const isPaymentLoading = ref(false);
@@ -141,19 +139,22 @@ const handleMembershipPurchase = async () => {
 };
 
 const handleUpgrade = async () => {
-  if (!upgradeForm.value.name || !upgradeForm.value.password) {
-    alert("请填写用户名和密码");
+  if (!upgradeForm.value.password || !upgradeForm.value.email) {
+    alert("请填写邮箱和密码");
     return;
   }
 
   isLoading.value = true;
   try {
+    // 使用临时用户的username作为正式用户的username
+    const tempUsername = userStore.userInfo.username || userStore.userInfo.name;
+    
     const response = await upgradeGuest(
       userStore.userInfo.id,
-      upgradeForm.value.name,
+      tempUsername, // 使用临时用户的username
       upgradeForm.value.password,
-      upgradeForm.value.email || undefined,
-      upgradeForm.value.phone || undefined
+      upgradeForm.value.email,
+      undefined // phone不再需要
     );
 
     // 更新用户信息
@@ -162,10 +163,8 @@ const handleUpgrade = async () => {
 
     // 重置表单
     upgradeForm.value = {
-      name: null,
       password: null,
-      email: undefined,
-      phone: undefined,
+      email: null,
     };
 
     alert("升级成功!");
@@ -375,18 +374,25 @@ onMounted(async () => {
       >
         <h3 class="text-xl font-semibold text-white/90 mb-4">成为正式用户</h3>
 
+        <div class="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4 mb-6">
+          <p class="text-sm text-blue-200">
+            升级后您将获得完整的账户功能,可以在任何设备上登录使用
+          </p>
+        </div>
+
         <form @submit.prevent="handleUpgrade" class="space-y-4">
           <div>
             <label class="block text-sm font-medium text-white/70 mb-1">
-              用户名 <span class="text-red-400">*</span>
+              邮箱 <span class="text-red-400">*</span>
             </label>
             <input
-              v-model="upgradeForm.name"
-              type="text"
+              v-model="upgradeForm.email"
+              type="email"
               required
               class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/40 focus:ring-2 focus:ring-brand focus:border-transparent"
-              placeholder="请输入用户名"
+              placeholder="请输入您的邮箱"
             />
+            <p class="mt-1 text-xs text-white/50">用于账号找回和重要通知</p>
           </div>
 
           <div>
@@ -398,32 +404,9 @@ onMounted(async () => {
               type="password"
               required
               class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/40 focus:ring-2 focus:ring-brand focus:border-transparent"
-              placeholder="请输入密码"
-            />
-          </div>
-
-          <div>
-            <label class="block text-sm font-medium text-white/70 mb-1">
-              邮箱
-            </label>
-            <input
-              v-model="upgradeForm.email"
-              type="email"
-              class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/40 focus:ring-2 focus:ring-brand focus:border-transparent"
-              placeholder="请输入邮箱(可选)"
-            />
-          </div>
-
-          <div>
-            <label class="block text-sm font-medium text-white/70 mb-1">
-              手机号
-            </label>
-            <input
-              v-model="upgradeForm.phone"
-              type="tel"
-              class="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/40 focus:ring-2 focus:ring-brand focus:border-transparent"
-              placeholder="请输入手机号(可选)"
+              placeholder="请设置登录密码"
             />
+            <p class="mt-1 text-xs text-white/50">至少6位字符,建议包含字母和数字</p>
           </div>
 
           <div class="flex gap-3 pt-4">