| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- <template>
- <div class="video-iframe-test-container">
- <div class="test-header">
- <h1>iframe 视频播放器测试</h1>
- <p class="description">
- 测试使用 iframe 在 HTTPS 页面中播放 HTTP 视频
- </p>
- </div>
- <div class="test-controls">
- <div class="input-group">
- <label>视频地址 (m3u8):</label>
- <input
- v-model="testVideoUrl"
- type="text"
- placeholder="输入 HTTP m3u8 视频地址"
- class="url-input"
- />
- <button @click="loadVideo" class="load-btn">加载视频</button>
- </div>
-
- <div class="info-box">
- <h3>测试说明:</h3>
- <ul>
- <li>输入 HTTP 协议的 m3u8 视频地址</li>
- <li>点击"加载视频"按钮</li>
- <li>视频将在 iframe 中播放</li>
- <li>如果成功,说明 iframe 方案可行</li>
- <li>如果失败,会显示错误信息</li>
- </ul>
- </div>
- <div class="status-box" v-if="statusMessage">
- <div :class="['status-message', statusType]">
- {{ statusMessage }}
- </div>
- </div>
- </div>
- <div class="video-wrapper">
- <div class="video-container" v-if="currentVideoUrl">
- <iframe
- ref="videoIframe"
- :src="iframeSrc"
- frameborder="0"
- allowfullscreen
- allow="autoplay; fullscreen; picture-in-picture; encrypted-media"
- class="video-iframe"
- @load="onIframeLoad"
- ></iframe>
- </div>
- <div v-else class="placeholder">
- <p>请输入视频地址并点击"加载视频"</p>
- <p class="example">
- 示例:http://cnd9103.jxcnjd.com/20250513/X7MFkhQH/hls/index.m3u8
- </p>
- </div>
- </div>
- <div class="log-box" v-if="logs.length > 0">
- <h3>日志信息:</h3>
- <div class="logs">
- <div v-for="(log, index) in logs" :key="index" :class="['log-item', log.type]">
- <span class="log-time">{{ log.time }}</span>
- <span class="log-message">{{ log.message }}</span>
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, computed, onMounted, onUnmounted } from "vue";
- const testVideoUrl = ref("http://cnd9103.jxcnjd.com/20250513/X7MFkhQH/hls/index.m3u8");
- const currentVideoUrl = ref("");
- const statusMessage = ref("");
- const statusType = ref<"success" | "error" | "info">("info");
- const videoIframe = ref<HTMLIFrameElement>();
- const logs = ref<Array<{ time: string; message: string; type: string }>>([]);
- // 构建 iframe src
- const iframeSrc = computed(() => {
- if (!currentVideoUrl.value) return "";
-
- // 使用独立的视频播放测试页面(HTTPS)
- // 使用绝对路径,确保在正式服务器上也能正确访问
- const playerPageUrl = `${window.location.origin}/video-player-test.html`;
- const encodedUrl = encodeURIComponent(currentVideoUrl.value);
-
- const fullUrl = `${playerPageUrl}?url=${encodedUrl}`;
- console.log('[VideoIframeTest] iframe src:', fullUrl);
- return fullUrl;
- });
- // 添加日志
- const addLog = (message: string, type: string = "info") => {
- const time = new Date().toLocaleTimeString();
- logs.value.unshift({ time, message, type });
- if (logs.value.length > 50) {
- logs.value.pop();
- }
- };
- // 加载视频
- const loadVideo = () => {
- if (!testVideoUrl.value.trim()) {
- statusMessage.value = "请输入视频地址";
- statusType.value = "error";
- return;
- }
- try {
- // 验证 URL
- const url = new URL(testVideoUrl.value.trim());
- if (url.protocol !== "http:" && url.protocol !== "https:") {
- statusMessage.value = "无效的 URL 协议";
- statusType.value = "error";
- return;
- }
- currentVideoUrl.value = testVideoUrl.value.trim();
- statusMessage.value = "正在加载视频...";
- statusType.value = "info";
- addLog(`开始加载视频: ${currentVideoUrl.value}`, "info");
-
- // 显示 iframe 的完整 URL
- const iframeUrl = iframeSrc.value;
- addLog(`iframe URL: ${iframeUrl}`, "info");
- addLog(`当前页面协议: ${window.location.protocol}`, "info");
- addLog(`视频协议: ${url.protocol}`, "info");
- } catch (error) {
- statusMessage.value = "无效的 URL 格式";
- statusType.value = "error";
- addLog(`URL 格式错误: ${error}`, "error");
- }
- };
- // iframe 加载完成
- const onIframeLoad = () => {
- addLog("iframe 加载完成", "success");
- statusMessage.value = "iframe 加载完成,等待视频加载...";
- statusType.value = "info";
-
- // 检查 iframe 内容是否被重定向
- try {
- if (videoIframe.value?.contentWindow) {
- const iframeUrl = videoIframe.value.contentWindow.location.href;
- addLog(`iframe 当前 URL: ${iframeUrl}`, "info");
-
- // 如果 iframe 被重定向到首页,说明有问题
- if (iframeUrl.includes('/') && !iframeUrl.includes('video-player-test.html')) {
- addLog("⚠️ iframe 可能被重定向了!", "error");
- statusMessage.value = "iframe 被重定向,可能是路由拦截或文件不存在";
- statusType.value = "error";
- }
- }
- } catch (e) {
- // 跨域情况下无法访问 iframe 内容,这是正常的
- addLog("无法访问 iframe 内容(跨域限制,这是正常的)", "info");
- }
- };
- // 处理来自 iframe 的消息
- const handleMessage = (event: MessageEvent) => {
- // 验证消息来源(可选)
- // if (event.origin !== window.location.origin) return;
-
- const { type, currentTime, duration, error: errorMsg, code, url } = event.data;
-
- switch (type) {
- case "video-play":
- addLog("视频开始播放", "success");
- statusMessage.value = "视频播放中";
- statusType.value = "success";
- break;
-
- case "video-pause":
- addLog("视频暂停", "info");
- break;
-
- case "video-timeupdate":
- if (currentTime !== undefined && duration !== undefined) {
- // 只在整数秒时记录
- if (Math.floor(currentTime) % 10 === 0) {
- addLog(`播放进度: ${Math.floor(currentTime)}s / ${Math.floor(duration)}s`, "info");
- }
- }
- break;
-
- case "video-loaded":
- addLog(`视频元数据加载完成,时长: ${Math.floor(duration)}秒`, "success");
- statusMessage.value = `视频加载成功,时长: ${Math.floor(duration)}秒`;
- statusType.value = "success";
- break;
-
- case "html-loaded":
- addLog(`HTML 文件已加载: ${event.data.url}`, "success");
- break;
-
- case "video-info":
- addLog(`视频信息: ${event.data.message}`, "info");
- break;
-
- case "video-error-msg":
- addLog(`视频错误消息: ${event.data.message}`, "error");
- statusMessage.value = event.data.message;
- statusType.value = "error";
- break;
-
- case "video-error":
- addLog(`视频播放错误: ${errorMsg}`, "error");
- statusMessage.value = `播放失败: ${errorMsg}`;
- statusType.value = "error";
-
- // 检查是否是混合内容错误
- if (errorMsg && errorMsg.includes("混合内容")) {
- addLog("⚠️ 这是混合内容错误,iframe 方案可能无法解决", "error");
- }
- break;
-
- case "video-fullscreenchange":
- addLog(`全屏状态: ${event.data.isFullscreen ? "全屏" : "退出全屏"}`, "info");
- break;
- }
- };
- // 监听消息
- onMounted(() => {
- window.addEventListener("message", handleMessage);
- addLog("测试页面已加载", "info");
- addLog(`当前页面协议: ${window.location.protocol}`, "info");
- });
- // 清理
- onUnmounted(() => {
- window.removeEventListener("message", handleMessage);
- });
- </script>
- <style scoped>
- .video-iframe-test-container {
- min-height: 100vh;
- background: #0f172a;
- color: #fff;
- padding: 20px;
- max-width: 1400px;
- margin: 0 auto;
- }
- .test-header {
- text-align: center;
- margin-bottom: 30px;
- }
- .test-header h1 {
- font-size: 28px;
- margin-bottom: 10px;
- }
- .description {
- color: #94a3b8;
- font-size: 14px;
- }
- .test-controls {
- margin-bottom: 30px;
- }
- .input-group {
- display: flex;
- flex-direction: column;
- gap: 10px;
- margin-bottom: 20px;
- }
- .input-group label {
- font-size: 14px;
- font-weight: 500;
- }
- .url-input {
- padding: 12px;
- border: 1px solid #334155;
- border-radius: 6px;
- background: #1e293b;
- color: #fff;
- font-size: 14px;
- }
- .url-input:focus {
- outline: none;
- border-color: #10b981;
- }
- .load-btn {
- padding: 12px 24px;
- background: #10b981;
- color: #fff;
- border: none;
- border-radius: 6px;
- font-size: 14px;
- font-weight: 500;
- cursor: pointer;
- transition: background 0.2s;
- }
- .load-btn:hover {
- background: #059669;
- }
- .info-box {
- background: #1e293b;
- border: 1px solid #334155;
- border-radius: 6px;
- padding: 15px;
- margin-bottom: 20px;
- }
- .info-box h3 {
- font-size: 16px;
- margin-bottom: 10px;
- }
- .info-box ul {
- list-style: none;
- padding: 0;
- }
- .info-box li {
- padding: 5px 0;
- font-size: 14px;
- color: #cbd5e1;
- }
- .info-box li::before {
- content: "• ";
- color: #10b981;
- font-weight: bold;
- }
- .status-box {
- margin-bottom: 20px;
- }
- .status-message {
- padding: 12px 16px;
- border-radius: 6px;
- font-size: 14px;
- }
- .status-message.success {
- background: #065f46;
- color: #6ee7b7;
- border: 1px solid #10b981;
- }
- .status-message.error {
- background: #7f1d1d;
- color: #fca5a5;
- border: 1px solid #ef4444;
- }
- .status-message.info {
- background: #1e3a8a;
- color: #93c5fd;
- border: 1px solid #3b82f6;
- }
- .video-wrapper {
- margin-bottom: 30px;
- }
- .video-container {
- position: relative;
- width: 100%;
- aspect-ratio: 16 / 9;
- background: #000;
- border-radius: 8px;
- overflow: hidden;
- }
- .video-iframe {
- width: 100%;
- height: 100%;
- border: none;
- display: block;
- }
- .placeholder {
- aspect-ratio: 16 / 9;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background: #1e293b;
- border: 2px dashed #334155;
- border-radius: 8px;
- color: #94a3b8;
- text-align: center;
- padding: 40px;
- }
- .placeholder p {
- margin: 10px 0;
- font-size: 16px;
- }
- .example {
- font-size: 12px !important;
- color: #64748b;
- word-break: break-all;
- }
- .log-box {
- background: #1e293b;
- border: 1px solid #334155;
- border-radius: 6px;
- padding: 15px;
- max-height: 300px;
- overflow-y: auto;
- }
- .log-box h3 {
- font-size: 16px;
- margin-bottom: 10px;
- }
- .logs {
- display: flex;
- flex-direction: column;
- gap: 5px;
- }
- .log-item {
- padding: 8px;
- border-radius: 4px;
- font-size: 12px;
- font-family: 'Courier New', monospace;
- }
- .log-item.info {
- background: #1e3a8a;
- color: #93c5fd;
- }
- .log-item.success {
- background: #065f46;
- color: #6ee7b7;
- }
- .log-item.error {
- background: #7f1d1d;
- color: #fca5a5;
- }
- .log-time {
- color: #64748b;
- margin-right: 10px;
- }
- .log-message {
- word-break: break-all;
- }
- @media (max-width: 768px) {
- .video-iframe-test-container {
- padding: 15px;
- }
-
- .test-header h1 {
- font-size: 24px;
- }
- }
- </style>
|