videoNavigation

岐黄天使刷课助手的视频播放页面导航模块,负责视频播放页面的上一课/下一课功能。

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/537082/1595111/videoNavigation.js

  1. // ==UserScript==
  2. // @name 岐黄天使刷课助手 - 视频导航模块
  3. // @namespace http://tampermonkey.net/qhtx-modules
  4. // @version 1.3.1
  5. // @description 岐黄天使刷课助手的视频播放页面导航模块,负责视频播放页面的上一课/下一课功能。
  6. // @author AI助手
  7. // ==/UserScript==
  8.  
  9. // 视频导航模块 - 专门处理视频播放页面的导航
  10. (function() {
  11. 'use strict';
  12.  
  13. // 初始化视频导航
  14. window.initVideoNavigation = function() {
  15. console.log('初始化视频导航');
  16.  
  17. // 创建导航按钮
  18. createNavigationButtons();
  19.  
  20. // 监听视频结束事件
  21. listenVideoEnd();
  22. };
  23.  
  24. // 创建导航按钮
  25. function createNavigationButtons() {
  26. // 检查是否已存在导航按钮
  27. if (document.querySelector('.qh-nav-buttons')) {
  28. console.log('导航按钮已存在,无需创建');
  29. return;
  30. }
  31.  
  32. console.log('创建导航按钮');
  33.  
  34. // 创建导航按钮容器
  35. const navContainer = document.createElement('div');
  36. navContainer.className = 'qh-nav-buttons';
  37. navContainer.style.cssText = `
  38. position: fixed;
  39. bottom: 20px;
  40. left: 0;
  41. right: 0;
  42. display: flex;
  43. justify-content: space-between;
  44. padding: 0 36px;
  45. z-index: 9999;
  46. `;
  47.  
  48. // 创建上一课按钮
  49. const prevButton = document.createElement('div');
  50. prevButton.className = 'qh-prev-btn';
  51. prevButton.textContent = '上一课';
  52. prevButton.style.cssText = `
  53. padding: 10px 20px;
  54. background-color: #40b65a;
  55. color: white;
  56. border-radius: 5px;
  57. cursor: pointer;
  58. text-align: center;
  59. user-select: none;
  60. `;
  61.  
  62. // 创建下一课按钮
  63. const nextButton = document.createElement('div');
  64. nextButton.className = 'qh-next-btn';
  65. nextButton.textContent = '下一课';
  66. nextButton.style.cssText = `
  67. padding: 10px 20px;
  68. background-color: #40b65a;
  69. color: white;
  70. border-radius: 5px;
  71. cursor: pointer;
  72. text-align: center;
  73. user-select: none;
  74. `;
  75.  
  76. // 添加按钮到容器
  77. navContainer.appendChild(prevButton);
  78. navContainer.appendChild(nextButton);
  79.  
  80. // 添加容器到页面
  81. document.body.appendChild(navContainer);
  82.  
  83. // 绑定事件
  84. prevButton.addEventListener('click', function() {
  85. navigateToPrevVideo();
  86. });
  87.  
  88. nextButton.addEventListener('click', function() {
  89. navigateToNextVideo();
  90. });
  91.  
  92. console.log('导航按钮创建完成');
  93. }
  94.  
  95. // 监听视频结束事件
  96. function listenVideoEnd() {
  97. // 定义视频结束处理函数
  98. window.videoEndHandler = function() {
  99. console.log('视频播放结束,准备切换到下一个视频');
  100.  
  101. // 防止重复触发
  102. if (window.qh.isNavigating) {
  103. console.log('导航已在进行中,忽略视频结束事件');
  104. return;
  105. }
  106.  
  107. // 延迟一秒后切换到下一课,避免与其他事件冲突
  108. setTimeout(() => {
  109. navigateToNextVideo();
  110. }, 1000);
  111. };
  112.  
  113. // 查找并监听所有视频元素
  114. function findAndListenVideos() {
  115. console.log('查找视频元素并添加结束事件监听器');
  116.  
  117. // 查找页面中的视频元素
  118. const videoElements = document.querySelectorAll('video');
  119. let foundVideos = false;
  120.  
  121. videoElements.forEach(video => {
  122. // 移除之前可能存在的事件监听器
  123. video.removeEventListener('ended', window.videoEndHandler);
  124.  
  125. // 添加事件监听器
  126. video.addEventListener('ended', window.videoEndHandler);
  127. console.log('已为视频添加结束事件监听器');
  128. foundVideos = true;
  129.  
  130. // 监听视频进度
  131. monitorVideoProgress(video);
  132. });
  133.  
  134. // 如果没有找到视频元素,尝试在iframe中查找
  135. if (!foundVideos) {
  136. const iframes = document.querySelectorAll('iframe');
  137. iframes.forEach(iframe => {
  138. try {
  139. if (!iframe.contentDocument && iframe.contentWindow) {
  140. console.log('尝试通过contentWindow访问iframe内容');
  141. const iframeDoc = iframe.contentWindow.document;
  142. processIframeVideos(iframeDoc);
  143. } else if (iframe.contentDocument) {
  144. console.log('尝试通过contentDocument访问iframe内容');
  145. const iframeDoc = iframe.contentDocument;
  146. processIframeVideos(iframeDoc);
  147. } else {
  148. console.log('无法访问iframe内容,可能是跨域限制');
  149. }
  150. } catch (e) {
  151. console.error('访问iframe内容时出错:', e);
  152. }
  153. });
  154. }
  155. }
  156.  
  157. // 处理iframe中的视频
  158. function processIframeVideos(iframeDoc) {
  159. if (!iframeDoc) {
  160. console.log('iframe文档为空');
  161. return;
  162. }
  163.  
  164. const iframeVideos = iframeDoc.querySelectorAll('video');
  165.  
  166. if (iframeVideos.length > 0) {
  167. console.log(`在iframe中找到 ${iframeVideos.length} 个视频元素`);
  168.  
  169. iframeVideos.forEach(video => {
  170. // 移除之前可能存在的事件监听器
  171. video.removeEventListener('ended', window.videoEndHandler);
  172.  
  173. // 添加事件监听器
  174. video.addEventListener('ended', window.videoEndHandler);
  175. console.log('已为iframe视频添加结束事件监听器');
  176.  
  177. // 监听视频进度
  178. monitorVideoProgress(video);
  179. });
  180. } else {
  181. console.log('在iframe中未找到视频元素');
  182. }
  183. }
  184.  
  185. // 监听视频进度,处理视频结束但未触发ended事件的情况
  186. function monitorVideoProgress(video) {
  187. // 创建进度监听器
  188. const progressInterval = setInterval(() => {
  189. if (!video || video.paused || video.ended || !video.duration) {
  190. return;
  191. }
  192.  
  193. // 计算进度百分比
  194. const progress = Math.floor((video.currentTime / video.duration) * 100);
  195.  
  196. // 如果进度接近100%(大于等于99%),认为视频已结束
  197. if (progress >= 99) {
  198. console.log('视频进度达到99%以上,视为已结束');
  199. clearInterval(progressInterval);
  200.  
  201. // 防止重复触发
  202. if (window.qh.isNavigating) {
  203. console.log('导航已在进行中,忽略视频进度事件');
  204. return;
  205. }
  206.  
  207. // 延迟一秒后切换到下一课
  208. setTimeout(() => {
  209. navigateToNextVideo();
  210. }, 1000);
  211. }
  212. }, 2000); // 每2秒检查一次
  213.  
  214. // 保存interval ID,以便在需要时清除
  215. if (!window.qh.progressIntervals) {
  216. window.qh.progressIntervals = [];
  217. }
  218. window.qh.progressIntervals.push(progressInterval);
  219. }
  220.  
  221. // 初始查找视频
  222. findAndListenVideos();
  223.  
  224. // 使用MutationObserver监听DOM变化,处理动态加载的视频
  225. const observer = new MutationObserver(function(mutations) {
  226. mutations.forEach(function(mutation) {
  227. if (mutation.addedNodes.length) {
  228. // 检查是否添加了视频元素或iframe
  229. const hasNewVideo = Array.from(mutation.addedNodes).some(node =>
  230. node.nodeName === 'VIDEO' ||
  231. (node.querySelectorAll && node.querySelectorAll('video').length > 0) ||
  232. node.nodeName === 'IFRAME' ||
  233. (node.querySelectorAll && node.querySelectorAll('iframe').length > 0)
  234. );
  235.  
  236. if (hasNewVideo) {
  237. console.log('检测到新的视频元素或iframe被添加到DOM,重新查找视频');
  238. findAndListenVideos();
  239. }
  240. }
  241. });
  242. });
  243.  
  244. // 配置观察选项
  245. const config = { childList: true, subtree: true };
  246.  
  247. // 开始观察
  248. observer.observe(document.body, config);
  249.  
  250. // 保存observer,以便在需要时断开连接
  251. window.qh.videoObserver = observer;
  252. }
  253.  
  254. // 导航到上一个视频
  255. function navigateToPrevVideo() {
  256. console.log('导航到上一个视频');
  257.  
  258. // 防止重复触发
  259. if (window.qh.isNavigating) {
  260. console.log('导航已在进行中,忽略本次请求');
  261. return;
  262. }
  263.  
  264. // 设置导航状态
  265. window.qh.isNavigating = true;
  266.  
  267. try {
  268. // 尝试查找原始的上一课按钮
  269. const originalPrevButton = document.querySelector('.prev-next .btns:first-child');
  270.  
  271. if (originalPrevButton) {
  272. console.log('找到原始上一课按钮,模拟点击');
  273.  
  274. // 模拟点击原始按钮
  275. const clickEvent = new MouseEvent('click', {
  276. bubbles: true,
  277. cancelable: true,
  278. view: window
  279. });
  280. originalPrevButton.dispatchEvent(clickEvent);
  281. } else {
  282. console.log('未找到原始上一课按钮,尝试其他方法');
  283.  
  284. // 尝试使用window.prev函数(如果存在)
  285. if (typeof window.prev === 'function') {
  286. console.log('使用window.prev函数');
  287. window.prev();
  288. }
  289. // 尝试使用脚本中的导航函数
  290. else if (typeof window.navigateToPrevCourse === 'function') {
  291. console.log('使用脚本导航函数navigateToPrevCourse');
  292. window.navigateToPrevCourse();
  293. }
  294. // 尝试查找其他可能的上一课按钮
  295. else {
  296. console.log('尝试查找其他可能的上一课按钮');
  297.  
  298. // 尝试查找包含"上一"文本的按钮或链接
  299. const prevElements = Array.from(document.querySelectorAll('a, button, div')).filter(el =>
  300. el.textContent && el.textContent.includes('上一') &&
  301. getComputedStyle(el).display !== 'none'
  302. );
  303.  
  304. if (prevElements.length > 0) {
  305. console.log('找到可能的上一课元素,点击第一个');
  306. prevElements[0].click();
  307. } else {
  308. console.log('未找到任何可能的上一课元素');
  309. alert('无法导航到上一课,请手动切换');
  310. }
  311. }
  312. }
  313. } catch (e) {
  314. console.error('导航到上一课出错:', e);
  315. }
  316.  
  317. // 5秒后重置导航状态
  318. setTimeout(() => {
  319. window.qh.isNavigating = false;
  320. }, 5000);
  321. }
  322.  
  323. // 导航到上一课的兼容函数(与UI模块保持一致)
  324. function navigateToPrevCourse() {
  325. console.log('[视频导航] 调用 navigateToPrevCourse (兼容函数)');
  326. navigateToPrevVideo();
  327. }
  328.  
  329. // 导航到下一个视频
  330. function navigateToNextVideo() {
  331. console.log('导航到下一个视频');
  332.  
  333. // 防止重复触发
  334. if (window.qh.isNavigating) {
  335. console.log('导航已在进行中,忽略本次请求');
  336. return;
  337. }
  338.  
  339. // 设置导航状态
  340. window.qh.isNavigating = true;
  341.  
  342. try {
  343. // 尝试查找原始的下一课按钮
  344. const originalNextButton = document.querySelector('.prev-next .btns:last-child');
  345.  
  346. if (originalNextButton) {
  347. console.log('找到原始下一课按钮,模拟点击');
  348.  
  349. // 模拟点击原始按钮
  350. const clickEvent = new MouseEvent('click', {
  351. bubbles: true,
  352. cancelable: true,
  353. view: window
  354. });
  355. originalNextButton.dispatchEvent(clickEvent);
  356. } else {
  357. console.log('未找到原始下一课按钮,尝试其他方法');
  358.  
  359. // 尝试使用window.next函数(如果存在)
  360. if (typeof window.next === 'function') {
  361. console.log('使用window.next函数');
  362. window.next();
  363. }
  364. // 尝试使用脚本中的导航函数
  365. else if (typeof window.navigateToNextCourse === 'function') {
  366. console.log('使用脚本导航函数navigateToNextCourse');
  367. window.navigateToNextCourse();
  368. }
  369. // 尝试查找其他可能的下一课按钮
  370. else {
  371. console.log('尝试查找其他可能的下一课按钮');
  372.  
  373. // 尝试查找包含"下一"文本的按钮或链接
  374. const nextElements = Array.from(document.querySelectorAll('a, button, div')).filter(el =>
  375. el.textContent && el.textContent.includes('下一') &&
  376. getComputedStyle(el).display !== 'none'
  377. );
  378.  
  379. if (nextElements.length > 0) {
  380. console.log('找到可能的下一课元素,点击第一个');
  381. nextElements[0].click();
  382. } else {
  383. console.log('未找到任何可能的下一课元素');
  384. alert('无法导航到下一课,请手动切换');
  385. }
  386. }
  387. }
  388. } catch (e) {
  389. console.error('导航到下一课出错:', e);
  390. }
  391.  
  392. // 5秒后重置导航状态
  393. setTimeout(() => {
  394. window.qh.isNavigating = false;
  395. }, 5000);
  396. }
  397.  
  398. // 导航到下一课的兼容函数(与UI模块保持一致)
  399. function navigateToNextCourse() {
  400. console.log('[视频导航] 调用 navigateToNextCourse (兼容函数)');
  401. navigateToNextVideo();
  402. }
  403.  
  404. // 导出函数
  405. window.navigateToPrevVideo = navigateToPrevVideo;
  406. window.navigateToNextVideo = navigateToNextVideo;
  407. window.navigateToPrevCourse = navigateToPrevCourse;
  408. window.navigateToNextCourse = navigateToNextCourse;
  409. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址