VideoIframeTest.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <template>
  2. <div class="video-iframe-test-container">
  3. <div class="test-header">
  4. <h1>iframe 视频播放器测试</h1>
  5. <p class="description">
  6. 测试使用 iframe 在 HTTPS 页面中播放 HTTP 视频
  7. </p>
  8. </div>
  9. <div class="test-controls">
  10. <div class="input-group">
  11. <label>视频地址 (m3u8):</label>
  12. <input
  13. v-model="testVideoUrl"
  14. type="text"
  15. placeholder="输入 HTTP m3u8 视频地址"
  16. class="url-input"
  17. />
  18. <button @click="loadVideo" class="load-btn">加载视频</button>
  19. </div>
  20. <div class="info-box">
  21. <h3>测试说明:</h3>
  22. <ul>
  23. <li>输入 HTTP 协议的 m3u8 视频地址</li>
  24. <li>点击"加载视频"按钮</li>
  25. <li>视频将在 iframe 中播放</li>
  26. <li>如果成功,说明 iframe 方案可行</li>
  27. <li>如果失败,会显示错误信息</li>
  28. </ul>
  29. </div>
  30. <div class="status-box" v-if="statusMessage">
  31. <div :class="['status-message', statusType]">
  32. {{ statusMessage }}
  33. </div>
  34. </div>
  35. </div>
  36. <div class="video-wrapper">
  37. <div class="video-container" v-if="currentVideoUrl">
  38. <iframe
  39. ref="videoIframe"
  40. :src="iframeSrc"
  41. frameborder="0"
  42. allowfullscreen
  43. allow="autoplay; fullscreen; picture-in-picture; encrypted-media"
  44. class="video-iframe"
  45. @load="onIframeLoad"
  46. ></iframe>
  47. </div>
  48. <div v-else class="placeholder">
  49. <p>请输入视频地址并点击"加载视频"</p>
  50. <p class="example">
  51. 示例:http://cnd9103.jxcnjd.com/20250513/X7MFkhQH/hls/index.m3u8
  52. </p>
  53. </div>
  54. </div>
  55. <div class="log-box" v-if="logs.length > 0">
  56. <h3>日志信息:</h3>
  57. <div class="logs">
  58. <div v-for="(log, index) in logs" :key="index" :class="['log-item', log.type]">
  59. <span class="log-time">{{ log.time }}</span>
  60. <span class="log-message">{{ log.message }}</span>
  61. </div>
  62. </div>
  63. </div>
  64. </div>
  65. </template>
  66. <script setup lang="ts">
  67. import { ref, computed, onMounted, onUnmounted } from "vue";
  68. const testVideoUrl = ref("http://cnd9103.jxcnjd.com/20250513/X7MFkhQH/hls/index.m3u8");
  69. const currentVideoUrl = ref("");
  70. const statusMessage = ref("");
  71. const statusType = ref<"success" | "error" | "info">("info");
  72. const videoIframe = ref<HTMLIFrameElement>();
  73. const logs = ref<Array<{ time: string; message: string; type: string }>>([]);
  74. // 构建 iframe src
  75. const iframeSrc = computed(() => {
  76. if (!currentVideoUrl.value) return "";
  77. // 使用独立的视频播放测试页面(HTTPS)
  78. // 使用绝对路径,确保在正式服务器上也能正确访问
  79. const playerPageUrl = `${window.location.origin}/video-player-test.html`;
  80. const encodedUrl = encodeURIComponent(currentVideoUrl.value);
  81. const fullUrl = `${playerPageUrl}?url=${encodedUrl}`;
  82. console.log('[VideoIframeTest] iframe src:', fullUrl);
  83. return fullUrl;
  84. });
  85. // 添加日志
  86. const addLog = (message: string, type: string = "info") => {
  87. const time = new Date().toLocaleTimeString();
  88. logs.value.unshift({ time, message, type });
  89. if (logs.value.length > 50) {
  90. logs.value.pop();
  91. }
  92. };
  93. // 加载视频
  94. const loadVideo = () => {
  95. if (!testVideoUrl.value.trim()) {
  96. statusMessage.value = "请输入视频地址";
  97. statusType.value = "error";
  98. return;
  99. }
  100. try {
  101. // 验证 URL
  102. const url = new URL(testVideoUrl.value.trim());
  103. if (url.protocol !== "http:" && url.protocol !== "https:") {
  104. statusMessage.value = "无效的 URL 协议";
  105. statusType.value = "error";
  106. return;
  107. }
  108. currentVideoUrl.value = testVideoUrl.value.trim();
  109. statusMessage.value = "正在加载视频...";
  110. statusType.value = "info";
  111. addLog(`开始加载视频: ${currentVideoUrl.value}`, "info");
  112. // 显示 iframe 的完整 URL
  113. const iframeUrl = iframeSrc.value;
  114. addLog(`iframe URL: ${iframeUrl}`, "info");
  115. addLog(`当前页面协议: ${window.location.protocol}`, "info");
  116. addLog(`视频协议: ${url.protocol}`, "info");
  117. } catch (error) {
  118. statusMessage.value = "无效的 URL 格式";
  119. statusType.value = "error";
  120. addLog(`URL 格式错误: ${error}`, "error");
  121. }
  122. };
  123. // iframe 加载完成
  124. const onIframeLoad = () => {
  125. addLog("iframe 加载完成", "success");
  126. statusMessage.value = "iframe 加载完成,等待视频加载...";
  127. statusType.value = "info";
  128. // 检查 iframe 内容是否被重定向
  129. try {
  130. if (videoIframe.value?.contentWindow) {
  131. const iframeUrl = videoIframe.value.contentWindow.location.href;
  132. addLog(`iframe 当前 URL: ${iframeUrl}`, "info");
  133. // 如果 iframe 被重定向到首页,说明有问题
  134. if (iframeUrl.includes('/') && !iframeUrl.includes('video-player-test.html')) {
  135. addLog("⚠️ iframe 可能被重定向了!", "error");
  136. statusMessage.value = "iframe 被重定向,可能是路由拦截或文件不存在";
  137. statusType.value = "error";
  138. }
  139. }
  140. } catch (e) {
  141. // 跨域情况下无法访问 iframe 内容,这是正常的
  142. addLog("无法访问 iframe 内容(跨域限制,这是正常的)", "info");
  143. }
  144. };
  145. // 处理来自 iframe 的消息
  146. const handleMessage = (event: MessageEvent) => {
  147. // 验证消息来源(可选)
  148. // if (event.origin !== window.location.origin) return;
  149. const { type, currentTime, duration, error: errorMsg, code, url } = event.data;
  150. switch (type) {
  151. case "video-play":
  152. addLog("视频开始播放", "success");
  153. statusMessage.value = "视频播放中";
  154. statusType.value = "success";
  155. break;
  156. case "video-pause":
  157. addLog("视频暂停", "info");
  158. break;
  159. case "video-timeupdate":
  160. if (currentTime !== undefined && duration !== undefined) {
  161. // 只在整数秒时记录
  162. if (Math.floor(currentTime) % 10 === 0) {
  163. addLog(`播放进度: ${Math.floor(currentTime)}s / ${Math.floor(duration)}s`, "info");
  164. }
  165. }
  166. break;
  167. case "video-loaded":
  168. addLog(`视频元数据加载完成,时长: ${Math.floor(duration)}秒`, "success");
  169. statusMessage.value = `视频加载成功,时长: ${Math.floor(duration)}秒`;
  170. statusType.value = "success";
  171. break;
  172. case "html-loaded":
  173. addLog(`HTML 文件已加载: ${event.data.url}`, "success");
  174. break;
  175. case "video-info":
  176. addLog(`视频信息: ${event.data.message}`, "info");
  177. break;
  178. case "video-error-msg":
  179. addLog(`视频错误消息: ${event.data.message}`, "error");
  180. statusMessage.value = event.data.message;
  181. statusType.value = "error";
  182. break;
  183. case "video-error":
  184. addLog(`视频播放错误: ${errorMsg}`, "error");
  185. statusMessage.value = `播放失败: ${errorMsg}`;
  186. statusType.value = "error";
  187. // 检查是否是混合内容错误
  188. if (errorMsg && errorMsg.includes("混合内容")) {
  189. addLog("⚠️ 这是混合内容错误,iframe 方案可能无法解决", "error");
  190. }
  191. break;
  192. case "video-fullscreenchange":
  193. addLog(`全屏状态: ${event.data.isFullscreen ? "全屏" : "退出全屏"}`, "info");
  194. break;
  195. }
  196. };
  197. // 监听消息
  198. onMounted(() => {
  199. window.addEventListener("message", handleMessage);
  200. addLog("测试页面已加载", "info");
  201. addLog(`当前页面协议: ${window.location.protocol}`, "info");
  202. });
  203. // 清理
  204. onUnmounted(() => {
  205. window.removeEventListener("message", handleMessage);
  206. });
  207. </script>
  208. <style scoped>
  209. .video-iframe-test-container {
  210. min-height: 100vh;
  211. background: #0f172a;
  212. color: #fff;
  213. padding: 20px;
  214. max-width: 1400px;
  215. margin: 0 auto;
  216. }
  217. .test-header {
  218. text-align: center;
  219. margin-bottom: 30px;
  220. }
  221. .test-header h1 {
  222. font-size: 28px;
  223. margin-bottom: 10px;
  224. }
  225. .description {
  226. color: #94a3b8;
  227. font-size: 14px;
  228. }
  229. .test-controls {
  230. margin-bottom: 30px;
  231. }
  232. .input-group {
  233. display: flex;
  234. flex-direction: column;
  235. gap: 10px;
  236. margin-bottom: 20px;
  237. }
  238. .input-group label {
  239. font-size: 14px;
  240. font-weight: 500;
  241. }
  242. .url-input {
  243. padding: 12px;
  244. border: 1px solid #334155;
  245. border-radius: 6px;
  246. background: #1e293b;
  247. color: #fff;
  248. font-size: 14px;
  249. }
  250. .url-input:focus {
  251. outline: none;
  252. border-color: #10b981;
  253. }
  254. .load-btn {
  255. padding: 12px 24px;
  256. background: #10b981;
  257. color: #fff;
  258. border: none;
  259. border-radius: 6px;
  260. font-size: 14px;
  261. font-weight: 500;
  262. cursor: pointer;
  263. transition: background 0.2s;
  264. }
  265. .load-btn:hover {
  266. background: #059669;
  267. }
  268. .info-box {
  269. background: #1e293b;
  270. border: 1px solid #334155;
  271. border-radius: 6px;
  272. padding: 15px;
  273. margin-bottom: 20px;
  274. }
  275. .info-box h3 {
  276. font-size: 16px;
  277. margin-bottom: 10px;
  278. }
  279. .info-box ul {
  280. list-style: none;
  281. padding: 0;
  282. }
  283. .info-box li {
  284. padding: 5px 0;
  285. font-size: 14px;
  286. color: #cbd5e1;
  287. }
  288. .info-box li::before {
  289. content: "• ";
  290. color: #10b981;
  291. font-weight: bold;
  292. }
  293. .status-box {
  294. margin-bottom: 20px;
  295. }
  296. .status-message {
  297. padding: 12px 16px;
  298. border-radius: 6px;
  299. font-size: 14px;
  300. }
  301. .status-message.success {
  302. background: #065f46;
  303. color: #6ee7b7;
  304. border: 1px solid #10b981;
  305. }
  306. .status-message.error {
  307. background: #7f1d1d;
  308. color: #fca5a5;
  309. border: 1px solid #ef4444;
  310. }
  311. .status-message.info {
  312. background: #1e3a8a;
  313. color: #93c5fd;
  314. border: 1px solid #3b82f6;
  315. }
  316. .video-wrapper {
  317. margin-bottom: 30px;
  318. }
  319. .video-container {
  320. position: relative;
  321. width: 100%;
  322. aspect-ratio: 16 / 9;
  323. background: #000;
  324. border-radius: 8px;
  325. overflow: hidden;
  326. }
  327. .video-iframe {
  328. width: 100%;
  329. height: 100%;
  330. border: none;
  331. display: block;
  332. }
  333. .placeholder {
  334. aspect-ratio: 16 / 9;
  335. display: flex;
  336. flex-direction: column;
  337. align-items: center;
  338. justify-content: center;
  339. background: #1e293b;
  340. border: 2px dashed #334155;
  341. border-radius: 8px;
  342. color: #94a3b8;
  343. text-align: center;
  344. padding: 40px;
  345. }
  346. .placeholder p {
  347. margin: 10px 0;
  348. font-size: 16px;
  349. }
  350. .example {
  351. font-size: 12px !important;
  352. color: #64748b;
  353. word-break: break-all;
  354. }
  355. .log-box {
  356. background: #1e293b;
  357. border: 1px solid #334155;
  358. border-radius: 6px;
  359. padding: 15px;
  360. max-height: 300px;
  361. overflow-y: auto;
  362. }
  363. .log-box h3 {
  364. font-size: 16px;
  365. margin-bottom: 10px;
  366. }
  367. .logs {
  368. display: flex;
  369. flex-direction: column;
  370. gap: 5px;
  371. }
  372. .log-item {
  373. padding: 8px;
  374. border-radius: 4px;
  375. font-size: 12px;
  376. font-family: 'Courier New', monospace;
  377. }
  378. .log-item.info {
  379. background: #1e3a8a;
  380. color: #93c5fd;
  381. }
  382. .log-item.success {
  383. background: #065f46;
  384. color: #6ee7b7;
  385. }
  386. .log-item.error {
  387. background: #7f1d1d;
  388. color: #fca5a5;
  389. }
  390. .log-time {
  391. color: #64748b;
  392. margin-right: 10px;
  393. }
  394. .log-message {
  395. word-break: break-all;
  396. }
  397. @media (max-width: 768px) {
  398. .video-iframe-test-container {
  399. padding: 15px;
  400. }
  401. .test-header h1 {
  402. font-size: 24px;
  403. }
  404. }
  405. </style>