Ver código fonte

更新依赖项,优化视频播放器功能,添加分享提示,改进路由管理,增强用户体验

wuyi 3 meses atrás
pai
commit
d95338d3ea
6 arquivos alterados com 1445 adições e 383 exclusões
  1. 920 112
      package-lock.json
  2. 1 0
      package.json
  3. 17 1
      src/components/layout/Footer.vue
  4. 19 2
      src/components/layout/MainLayout.vue
  5. 349 134
      src/views/VideoPlayer.vue
  6. 139 134
      yarn.lock

Diferenças do arquivo suprimidas por serem muito extensas
+ 920 - 112
package-lock.json


+ 1 - 0
package.json

@@ -13,6 +13,7 @@
     "@types/axios": "^0.14.4",
     "@vueuse/core": "^13.9.0",
     "axios": "^1.12.2",
+    "hls.js": "^1.6.13",
     "pinia": "^2.1.0",
     "primeicons": "^7.0.0",
     "primevue": "^4.3.9",

+ 17 - 1
src/components/layout/Footer.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { useUserStore } from "@/store/user";
+import { useRouter } from "vue-router";
 import { computed } from "vue";
 
 type TabKey = "home" | "purchased" | "account" | "favorite";
@@ -13,10 +14,25 @@ defineProps<{
 }>();
 
 const userStore = useUserStore();
+const router = useRouter();
 const isLoggedIn = computed(() => !!userStore.token);
 
 function switchTab(key: TabKey) {
-  emit("switch-tab", key);
+  // 如果点击已购买但未登录,显示登录弹窗
+  if (key === "purchased" && !isLoggedIn.value) {
+    emit("switch-tab", key);
+    return;
+  }
+
+  // 进行实际的路由跳转
+  const routeMap: Record<TabKey, string> = {
+    home: "/",
+    purchased: "/purchased",
+    account: "/account",
+    favorite: "/favorite",
+  };
+
+  router.push(routeMap[key]);
   window.scrollTo({ top: 0, behavior: "smooth" });
 }
 </script>

+ 19 - 2
src/components/layout/MainLayout.vue

@@ -37,7 +37,8 @@
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted, onBeforeUnmount, computed } from "vue";
+import { ref, onMounted, onBeforeUnmount, computed, watch } from "vue";
+import { useRoute } from "vue-router";
 import Header from "@/components/layout/Header.vue";
 import Footer from "@/components/layout/Footer.vue";
 import LoginDialog from "@/components/LoginDialog.vue";
@@ -45,6 +46,7 @@ import { useUserStore } from "@/store/user";
 
 type TabKey = "home" | "purchased" | "account" | "favorite";
 
+const route = useRoute();
 const active = ref<TabKey>("home");
 const showScrollTop = ref(false);
 const showLoginDialog = ref(false);
@@ -52,6 +54,21 @@ const userStore = useUserStore();
 
 const isLoggedIn = computed(() => !!userStore.token);
 
+// 根据路由更新active状态
+function updateActiveFromRoute() {
+  const pathToTab: Record<string, TabKey> = {
+    "/": "home",
+    "/purchased": "purchased",
+    "/account": "account",
+    "/favorite": "favorite",
+  };
+
+  active.value = pathToTab[route.path] || "home";
+}
+
+// 监听路由变化
+watch(() => route.path, updateActiveFromRoute, { immediate: true });
+
 function handleScroll() {
   showScrollTop.value = window.scrollY > 320;
 }
@@ -65,7 +82,7 @@ function switchTab(key: TabKey) {
     showLoginDialog.value = true;
     return;
   }
-  active.value = key;
+  // 这里不再需要手动设置active,因为路由变化会自动更新
 }
 
 function handleLoginSuccess() {

+ 349 - 134
src/views/VideoPlayer.vue

@@ -22,13 +22,12 @@
         <span class="text-sm">返回</span>
       </button>
 
-      <div class="flex items-center gap-2">
+      <div class="relative">
         <button
-          @click="toggleFavorite"
+          @click="toggleShare"
           class="p-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition"
         >
           <svg
-            v-if="!isFavorite"
             class="w-5 h-5"
             fill="none"
             stroke="currentColor"
@@ -38,55 +37,75 @@
               stroke-linecap="round"
               stroke-linejoin="round"
               stroke-width="2"
-              d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
-            />
-          </svg>
-          <svg
-            v-else
-            class="w-5 h-5 fill-red-500 text-red-500"
-            viewBox="0 0 24 24"
-          >
-            <path
-              d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
+              d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z"
             />
           </svg>
         </button>
 
-        <button
-          @click="toggleShare"
-          class="p-2 rounded-lg bg-white/5 border border-white/10 text-white/70 hover:bg-white/10 hover:text-white transition"
+        <!-- 分享提示弹窗 -->
+        <div
+          v-if="showShareModal"
+          class="absolute top-0 left-full ml-2 z-50 bg-emerald-500 text-white px-3 py-1.5 rounded-lg shadow-lg flex items-center gap-1.5 whitespace-nowrap"
         >
-          <svg
-            class="w-5 h-5"
-            fill="none"
-            stroke="currentColor"
-            viewBox="0 0 24 24"
-          >
+          <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
             <path
-              stroke-linecap="round"
-              stroke-linejoin="round"
-              stroke-width="2"
-              d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z"
+              fill-rule="evenodd"
+              d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
+              clip-rule="evenodd"
             />
           </svg>
-        </button>
+          <span class="text-xs font-medium">已复制</span>
+        </div>
       </div>
     </div>
 
     <!-- 视频播放器区域 -->
     <div class="relative rounded-2xl overflow-hidden bg-black">
-      <div class="aspect-video">
+      <div class="aspect-video video-container">
         <video
           ref="videoPlayer"
-          :src="videoInfo.m3u8"
           :poster="videoInfo.cover"
           class="w-full h-full object-contain"
           controls
           preload="metadata"
           playsinline
+          webkit-playsinline
+          x5-playsinline
+          x5-video-player-type="h5"
+          x5-video-player-fullscreen="true"
+          x5-video-orientation="landscape"
+          @loadstart="onVideoLoadStart"
+          @loadeddata="onVideoLoadedData"
+          @error="onVideoError"
+          @canplay="onVideoCanPlay"
         >
           您的浏览器不支持视频播放
         </video>
+
+        <!-- 视频错误提示 -->
+        <div
+          v-if="videoError"
+          class="absolute inset-0 flex items-center justify-center bg-black/80 text-white"
+        >
+          <div class="text-center p-6">
+            <div class="text-4xl mb-4">⚠️</div>
+            <h3 class="text-lg font-semibold mb-2">视频加载失败</h3>
+            <p class="text-sm text-white/70 mb-4">{{ videoError }}</p>
+            <button
+              v-if="showRetryButton"
+              @click="retryVideoLoad"
+              :disabled="!canRetry"
+              class="px-4 py-2 rounded-lg transition"
+              :class="
+                canRetry
+                  ? 'bg-emerald-500 text-white hover:bg-emerald-600'
+                  : 'bg-gray-500 text-gray-300 cursor-not-allowed'
+              "
+            >
+              重试
+            </button>
+          </div>
+        </div>
       </div>
     </div>
 
@@ -175,9 +194,9 @@
       <!-- 相关推荐 -->
       <div v-if="relatedVideos.length > 0" class="space-y-4">
         <h3 class="text-sm font-medium text-white/80">相关推荐</h3>
-        <div class="grid grid-cols-3 gap-2">
+        <div class="grid grid-cols-2 md:grid-cols-3 gap-2">
           <article
-            v-for="video in relatedVideos"
+            v-for="video in relatedVideos.slice(0, 15)"
             :key="video.id"
             @click="playVideo(video)"
             class="group rounded-xl overflow-hidden bg-white/5 border border-white/10 cursor-pointer hover:bg-white/10 transition"
@@ -197,7 +216,7 @@
             </div>
             <div class="p-2">
               <h4
-                class="text-xs font-medium text-white/90 truncate leading-tight"
+                class="text-xs font-medium text-white/90 leading-tight line-clamp-2"
               >
                 {{ video.name }}
               </h4>
@@ -209,73 +228,14 @@
         </div>
       </div>
     </div>
-
-    <!-- 分享弹窗 -->
-    <div
-      v-if="showShareModal"
-      class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
-      @click="closeShareModal"
-    >
-      <div
-        class="bg-surface rounded-2xl p-6 max-w-sm w-full mx-4 border border-white/10"
-        @click.stop
-      >
-        <h3 class="text-lg font-semibold text-white mb-4">分享视频</h3>
-        <div class="space-y-3">
-          <button
-            @click="copyVideoLink"
-            class="w-full flex items-center gap-3 p-3 rounded-xl bg-white/5 border border-white/10 text-white/80 hover:bg-white/10 transition"
-          >
-            <svg
-              class="w-5 h-5"
-              fill="none"
-              stroke="currentColor"
-              viewBox="0 0 24 24"
-            >
-              <path
-                stroke-linecap="round"
-                stroke-linejoin="round"
-                stroke-width="2"
-                d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
-              />
-            </svg>
-            <span>复制链接</span>
-          </button>
-          <button
-            @click="shareToSocial"
-            class="w-full flex items-center gap-3 p-3 rounded-xl bg-white/5 border border-white/10 text-white/80 hover:bg-white/10 transition"
-          >
-            <svg
-              class="w-5 h-5"
-              fill="none"
-              stroke="currentColor"
-              viewBox="0 0 24 24"
-            >
-              <path
-                stroke-linecap="round"
-                stroke-linejoin="round"
-                stroke-width="2"
-                d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.367 2.684 3 3 0 00-5.367-2.684z"
-              />
-            </svg>
-            <span>分享到社交平台</span>
-          </button>
-        </div>
-        <button
-          @click="closeShareModal"
-          class="w-full mt-4 py-2 text-white/60 hover:text-white transition"
-        >
-          取消
-        </button>
-      </div>
-    </div>
   </section>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from "vue";
+import { ref, onMounted, onUnmounted, computed } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { searchVideoByTags } from "@/services/api";
+import Hls from "hls.js";
 
 // 路由相关
 const route = useRoute();
@@ -284,6 +244,9 @@ const router = useRouter();
 // 视频播放器引用
 const videoPlayer = ref<HTMLVideoElement>();
 
+// HLS实例
+let hls: Hls | null = null;
+
 // 视频信息
 const videoInfo = ref<any>({
   id: "",
@@ -301,8 +264,13 @@ const videoInfo = ref<any>({
 const relatedVideos = ref<any[]>([]);
 
 // 状态管理
-const isFavorite = ref(false);
 const showShareModal = ref(false);
+const videoError = ref<string>("");
+const retryCount = ref(0);
+const maxRetries = 3;
+const lastRetryTime = ref(0);
+const retryCooldown = 3000; // 3秒冷却时间
+const forceUpdate = ref(0); // 强制更新触发器
 
 // 生成设备标识
 const generateMacAddress = (): string => {
@@ -318,6 +286,19 @@ const generateMacAddress = (): string => {
 
 const device = generateMacAddress();
 
+// 计算属性
+const canRetry = computed(() => {
+  forceUpdate.value; // 依赖forceUpdate来触发重新计算
+  const now = Date.now();
+  const isCooldownActive = now - lastRetryTime.value < retryCooldown;
+  const hasRetriesLeft = retryCount.value < maxRetries;
+  return !isCooldownActive && hasRetriesLeft;
+});
+
+const showRetryButton = computed(() => {
+  return retryCount.value < maxRetries;
+});
+
 // 格式化时长
 const formatDuration = (duration: string | number): string => {
   const seconds = parseInt(String(duration));
@@ -342,45 +323,233 @@ const handleImageError = (event: Event) => {
     "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjMzMzIi8+CjxwYXRoIGQ9Ik0xMDAgNzVMMTI1IDEwMEgxMDBWMTI1SDc1TDEwMCAxMDBaIiBmaWxsPSIjNjY2Ii8+Cjwvc3ZnPgo=";
 };
 
-// 返回上一页
-const goBack = () => {
-  router.back();
+// 视频播放器事件处理
+const onVideoLoadStart = () => {
+  console.log("视频开始加载...");
 };
 
-// 切换收藏状态
-const toggleFavorite = () => {
-  isFavorite.value = !isFavorite.value;
-  // TODO: 实现收藏功能
+const onVideoLoadedData = () => {
+  console.log("视频数据加载完成");
 };
 
-// 切换分享弹窗
-const toggleShare = () => {
-  showShareModal.value = true;
+const onVideoCanPlay = () => {
+  console.log("视频可以播放");
 };
 
-// 关闭分享弹窗
-const closeShareModal = () => {
-  showShareModal.value = false;
+const onVideoError = (event: Event) => {
+  const video = event.target as HTMLVideoElement;
+  console.error("视频播放错误:", video.error);
+  console.error("错误详情:", {
+    code: video.error?.code,
+    message: video.error?.message,
+    networkState: video.networkState,
+    readyState: video.readyState,
+  });
+
+  // 设置统一的错误信息
+  videoError.value = "视频加载失败";
+};
+
+// 重试视频加载
+const retryVideoLoad = () => {
+  const now = Date.now();
+
+  // 检查是否在冷却时间内
+  if (now - lastRetryTime.value < retryCooldown) {
+    const remainingTime = Math.ceil(
+      (retryCooldown - (now - lastRetryTime.value)) / 1000
+    );
+    console.log(`重试冷却中,还需等待 ${remainingTime} 秒`);
+    return;
+  }
+
+  // 检查是否超过最大重试次数
+  if (retryCount.value >= maxRetries) {
+    console.log("已达到最大重试次数");
+    return;
+  }
+
+  // 更新重试状态
+  retryCount.value++;
+  lastRetryTime.value = now;
+
+  console.log(`第 ${retryCount.value} 次重试`);
+
+  videoError.value = "";
+  destroyHls();
+
+  // 延迟重新初始化
+  setTimeout(() => {
+    initHlsPlayer();
+  }, 500);
 };
 
-// 复制视频链接
-const copyVideoLink = async () => {
+// 初始化HLS播放器
+const initHlsPlayer = () => {
+  if (!videoPlayer.value) return;
+
+  const video = videoPlayer.value;
+  const videoSrc = videoInfo.value.m3u8;
+
+  if (!videoSrc) {
+    console.error("没有视频源地址");
+    return;
+  }
+
+  // console.log("初始化HLS播放器,视频源:", videoSrc);
+  console.log("初始化HLS播放器");
+
+  // 检查浏览器是否原生支持HLS
+  if (video.canPlayType("application/vnd.apple.mpegurl")) {
+    // Safari原生支持HLS
+    console.log("使用原生HLS支持");
+    video.src = videoSrc;
+  } else if (Hls.isSupported()) {
+    // 使用HLS.js
+    console.log("使用HLS.js支持");
+
+    // 先检测URL是否为HLS格式
+    detectVideoFormat(videoSrc)
+      .then((format) => {
+        if (format === "hls") {
+          initHlsJsPlayer(videoSrc, video);
+        } else {
+          console.log("检测到非HLS格式,尝试直接播放");
+          video.src = videoSrc;
+        }
+      })
+      .catch((error) => {
+        console.error("格式检测失败,尝试直接播放:", error);
+        video.src = videoSrc;
+      });
+  } else {
+    console.error("浏览器不支持HLS播放");
+    video.src = videoSrc;
+  }
+};
+
+// 检测视频格式
+const detectVideoFormat = async (url: string): Promise<"hls" | "direct"> => {
+  try {
+    const response = await fetch(url, {
+      method: "HEAD",
+      mode: "cors",
+    });
+
+    const contentType = response.headers.get("content-type") || "";
+    console.log("Content-Type:", contentType);
+
+    // 检查是否为HLS格式
+    if (
+      contentType.includes("application/vnd.apple.mpegurl") ||
+      contentType.includes("application/x-mpegURL") ||
+      url.includes(".m3u8")
+    ) {
+      return "hls";
+    }
+
+    // 检查是否为直接视频文件
+    if (contentType.includes("video/")) {
+      return "direct";
+    }
+
+    // 如果无法确定,尝试获取前几个字节来判断
+    const textResponse = await fetch(url, {
+      method: "GET",
+      mode: "cors",
+      headers: { Range: "bytes=0-1023" },
+    });
+    const text = await textResponse.text();
+
+    if (text.startsWith("#EXTM3U")) {
+      return "hls";
+    }
+
+    return "direct";
+  } catch (error) {
+    console.error("格式检测失败:", error);
+    return "direct";
+  }
+};
+
+// 初始化HLS.js播放器
+const initHlsJsPlayer = (videoSrc: string, video: HTMLVideoElement) => {
+  hls = new Hls({
+    debug: false, // 关闭调试日志
+    enableWorker: true,
+    lowLatencyMode: true,
+  });
+
+  hls.loadSource(videoSrc);
+  hls.attachMedia(video);
+
+  hls.on(Hls.Events.MANIFEST_PARSED, () => {
+    console.log("HLS清单解析完成,可以播放");
+  });
+
+  hls.on(Hls.Events.ERROR, (event, data) => {
+    console.error("HLS错误:", data);
+    if (data.fatal) {
+      switch (data.type) {
+        case Hls.ErrorTypes.NETWORK_ERROR:
+          console.error("网络错误,尝试恢复...");
+          if (data.details === "manifestParsingError") {
+            videoError.value = "视频加载失败";
+            // 销毁HLS实例,尝试直接播放
+            hls?.destroy();
+            hls = null;
+            video.src = videoSrc;
+          } else {
+            hls?.startLoad();
+          }
+          break;
+        case Hls.ErrorTypes.MEDIA_ERROR:
+          console.error("媒体错误,尝试恢复...");
+          hls?.recoverMediaError();
+          break;
+        default:
+          console.error("无法恢复的错误,尝试直接播放");
+          videoError.value = "视频加载失败";
+          // 销毁HLS实例,尝试直接播放
+          hls?.destroy();
+          hls = null;
+          video.src = videoSrc;
+          break;
+      }
+    }
+  });
+};
+
+// 清理HLS实例
+const destroyHls = () => {
+  if (hls) {
+    console.log("清理HLS实例");
+    hls.destroy();
+    hls = null;
+  }
+};
+
+// 返回上一页
+const goBack = () => {
+  router.back();
+};
+
+// 分享视频(直接复制链接并显示提示)
+const toggleShare = async () => {
   try {
     const videoUrl = window.location.href;
     await navigator.clipboard.writeText(videoUrl);
-    // TODO: 显示复制成功提示
-    closeShareModal();
+    showShareModal.value = true;
+
+    // 1秒后自动隐藏提示
+    setTimeout(() => {
+      showShareModal.value = false;
+    }, 1000);
   } catch (error) {
     console.error("复制链接失败:", error);
   }
 };
 
-// 分享到社交平台
-const shareToSocial = () => {
-  // TODO: 实现社交平台分享
-  closeShareModal();
-};
-
 // 播放视频
 const playVideo = (video: any) => {
   router.push({
@@ -412,7 +581,7 @@ const loadVideoInfo = () => {
       id: videoId,
       name: videoData.name || `视频 ${videoId}`,
       cover: videoData.cover || "",
-      m3u8: videoData.m3u8 || "",
+      m3u8: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
       duration: videoData.duration || 0,
       view: videoData.view || 0,
       like: videoData.like || 0,
@@ -422,6 +591,8 @@ const loadVideoInfo = () => {
 
     // 设置页面标题
     document.title = `${videoInfo.value.name} - 视频播放`;
+
+    // console.log("加载视频信息:", videoInfo.value);
   } else {
     console.error("未找到视频ID");
   }
@@ -484,29 +655,73 @@ const loadRelatedVideos = async () => {
 onMounted(() => {
   loadVideoInfo();
   loadRelatedVideos();
+
+  // 延迟初始化HLS播放器,确保DOM已渲染
+  setTimeout(() => {
+    initHlsPlayer();
+  }, 100);
+
+  // 启动定时器更新按钮状态
+  const timer = setInterval(() => {
+    if (videoError.value && retryCount.value < maxRetries) {
+      forceUpdate.value++; // 触发计算属性重新计算
+    }
+  }, 1000);
+
+  // 组件卸载时清除定时器
+  onUnmounted(() => {
+    clearInterval(timer);
+  });
+});
+
+onUnmounted(() => {
+  destroyHls();
 });
 </script>
 
 <style scoped>
-/* 自定义视频播放器样式 */
-video::-webkit-media-controls-panel {
-  background-color: rgba(0, 0, 0, 0.5);
+/* 视频容器样式 */
+.video-container {
+  position: relative;
 }
 
-video::-webkit-media-controls-play-button,
-video::-webkit-media-controls-pause-button {
-  background-color: rgba(255, 255, 255, 0.8);
-  border-radius: 50%;
-}
+/* 移动端全屏样式 */
+@media screen and (max-width: 768px) {
+  video {
+    /* 强制横屏全屏 */
+    object-fit: contain;
+  }
 
-video::-webkit-media-controls-timeline {
-  background-color: rgba(255, 255, 255, 0.3);
-  border-radius: 2px;
-}
+  /* 全屏时的样式 */
+  video:fullscreen {
+    width: 100vw;
+    height: 100vh;
+    object-fit: contain;
+    background: black;
+  }
+
+  /* WebKit全屏样式 */
+  video:-webkit-full-screen {
+    width: 100vw;
+    height: 100vh;
+    object-fit: contain;
+    background: black;
+  }
+
+  /* Mozilla全屏样式 */
+  video:-moz-full-screen {
+    width: 100vw;
+    height: 100vh;
+    object-fit: contain;
+    background: black;
+  }
 
-video::-webkit-media-controls-current-time-display,
-video::-webkit-media-controls-time-remaining-display {
-  color: white;
-  font-size: 12px;
+  /* MS全屏样式 */
+  video:-ms-fullscreen {
+    width: 100vw;
+    height: 100vh;
+    object-fit: contain;
+    background: black;
+  }
 }
 </style>

Diferenças do arquivo suprimidas por serem muito extensas
+ 139 - 134
yarn.lock


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff