您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
华医岗位课程自动播放工具:支持课程自动播放、倍速调节、全局静音切换、自动跳转下一课
// ==UserScript== // @name 华医网课脚本 // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 华医岗位课程自动播放工具:支持课程自动播放、倍速调节、全局静音切换、自动跳转下一课 // @author ssxbs // @match https://jcpxkh.91huayi.com/exercise/ExerciseHome/index* // @match *://*/*courseware_id* // @grant none // ==/UserScript== (function () { 'use strict'; // 配置参数:新增首次加载优化相关配置 const CONFIG = { // 原有配置保持不变... POST_COURSE_SEL: 'div.navItem[id="/Exercise/ExerciseCourse/ExcellentIndex?package=true"]', IFRAME_SEL: '#student_iframe', COURSE_LIST_SEL: '.itemList', COURSE_ITEM_SEL: '.contentItem', COURSE_PLAY_PATHS: [ 'exercise/ExerciseCourse/CoursePlay', 'exercise/ExerciseCourse/BJYCoursePlay' ], PAGE_CLOSE_DELAY: 2000, PAGE_CLOSE_RETRY: 2, DEBUG_MODE: true, PANEL_STYLE: { position: 'fixed', top: '10px', left: '10px', backgroundColor: 'rgba(0,0,0,0.8)', color: '#fff', padding: '12px', borderRadius: '8px', fontSize: '14px', zIndex: '99999', minWidth: '320px', maxHeight: '80vh', overflowY: 'auto', boxShadow: '0 4px 12px rgba(0,0,0,0.3)' }, DRAG_HANDLE_STYLE: { height: '24px', cursor: 'move', margin: '-12px -12px 12px -12px', padding: '4px 12px', backgroundColor: 'rgba(50,50,100,0.5)', borderRadius: '8px 8px 0 0', display: 'flex', alignItems: 'center', justifyContent: 'center', userSelect: 'none' }, COURSE_ITEM_STYLE: { padding: '8px', margin: '6px 0', backgroundColor: 'rgba(255,255,255,0.1)', borderRadius: '4px', cursor: 'pointer', transition: 'background-color 0.3s', display: 'flex', alignItems: 'center' }, COURSE_ITEM_HOVER_STYLE: { backgroundColor: 'rgba(255,255,255,0.2)' }, COURSE_ITEM_PLAYING_STYLE: { backgroundColor: 'rgba(66, 133, 244, 0.3)', border: '1px solid #4285f4', boxShadow: '0 0 8px rgba(66, 133, 244, 0.5)' }, BUTTON_STYLE: { padding: '6px 10px', border: 'none', borderRadius: '4px', cursor: 'pointer', transition: 'all 0.2s', fontSize: '13px', fontWeight: '500', boxShadow: '0 1px 3px rgba(0,0,0,0.2)' }, BUTTON_HOVER_STYLE: { transform: 'translateY(-1px)', boxShadow: '0 2px 5px rgba(0,0,0,0.3)' }, BUTTON_COLORS: { speed: { background: '#4285f4', color: '#fff' }, mute: { background: '#fbbc05', color: '#000' } }, LIST_TOGGLE_STYLE: { padding: '3px 8px', border: '1px solid rgba(255,255,255,0.3)', borderRadius: '4px', backgroundColor: 'rgba(255,255,255,0.1)', color: '#fff', cursor: 'pointer', fontSize: '14px', transition: 'all 0.2s' }, CHECK_INTERVAL: 1500, INIT_DELAY: 2000, DEFAULT_SPEED: 1.0, BPLAYER_SPEED: 2.0, DETECT_INTERVAL_MS: 1000, STARTUP_PROGRESS_CHECK_TIME: 30000, LEARNED_COUNTDOWN_SECONDS: 10, AUTO_PLAY_RETRY_INTERVAL_MS: 1000, PROGRESS_CHANGE_THRESHOLD: 0.1, MAX_NO_PROGRESS_COUNT: 30, PROGRESS_STALL_RETRY_SECONDS: 10, AUTO_MUTE_ON_PLAY: true, VOLUME_CONTROL_SELECTORS: [ '#speaker', '.ccH5vm', '.volume-btn', '.bplayer-volume-btn' ], VOLUME_SLIDER_SEL: '.volume-slider, .bplayer-volume-slider, .ccH5VolumeSlider', // 新增:首次加载优化配置 FIRST_LOAD_DELAY: 3000, // 首次加载额外延迟(ms) RESOURCE_CHECK_RETRY: 5, // 资源检查重试次数 RESOURCE_CHECK_INTERVAL: 1000, // 资源检查间隔(ms) FIRST_JUMP_DELAY: 1500, // 首次跳转额外延迟(ms) MIN_LOADING_TIME: 2000, // 最小加载时间(ms) INITIAL_OPERATION_DELAY: 1000 // 初始操作延迟(ms) }; // 全局变量:新增首次加载状态标记 let hasClickedPostCourse = false; let iframeDoc = null; let pendingCourses = []; let mainPanel = null; let checkIframeTimer = null; let updateCourseTimer = null; let playbackSpeed = CONFIG.DEFAULT_SPEED; let hasPlayedNext = false; let speedChecked = false; let detectorTimerId = null; let startupProgressTimerId = null; let countdownTimerId = null; let learnedCountdownTimerId = null; let countdownTimeRemaining = CONFIG.MAX_NO_PROGRESS_COUNT; let learnedCountdownTimeRemaining = CONFIG.LEARNED_COUNTDOWN_SECONDS; let isLearnedCountdownActive = false; let autoPlayIntervalId = null; let initialProgress = 0; let currentCourseId = null; let lastProgress = 0; let noProgressCount = 0; let isJumping = false; let hasAutoJumped = false; let isGlobalMuted = false; let muteAttempts = 0; const MAX_MUTE_ATTEMPTS = 5; let jumpAttempts = 0; const MAX_JUMP_ATTEMPTS = 3; let isDragging = false; let offsetX = 0; let offsetY = 0; let isCourseListExpanded = true; let userMuteToggle = false; // 新增:首次加载状态跟踪 let isFirstLoad = true; // 是否首次加载 let pageLoadStartTime = 0; // 页面加载开始时间 let resourceCheckAttempts = 0; // 资源检查尝试次数 // 调试日志 const log = (message) => { if (CONFIG.DEBUG_MODE) console.log(`[华医脚本] ${message}`); }; // 面板拖拽功能 const makePanelDraggable = () => { const dragHandle = document.createElement('div'); dragHandle.id = 'huayiDragHandle'; dragHandle.textContent = '拖动面板'; Object.keys(CONFIG.DRAG_HANDLE_STYLE).forEach(key => { dragHandle.style[key] = CONFIG.DRAG_HANDLE_STYLE[key]; }); mainPanel.insertBefore(dragHandle, mainPanel.firstChild); dragHandle.addEventListener('mousedown', (e) => { isDragging = true; const rect = mainPanel.getBoundingClientRect(); offsetX = e.clientX - rect.left; offsetY = e.clientY - rect.top; mainPanel.style.transition = 'none'; }); document.addEventListener('mousemove', (e) => { if (!isDragging || !mainPanel) return; const x = e.clientX - offsetX; const y = e.clientY - offsetY; const maxX = window.innerWidth - mainPanel.offsetWidth; const maxY = window.innerHeight - mainPanel.offsetHeight; const boundedX = Math.max(0, Math.min(x, maxX)); const boundedY = Math.max(0, Math.min(y, maxY)); mainPanel.style.left = `${boundedX}px`; mainPanel.style.top = `${boundedY}px`; }); document.addEventListener('mouseup', () => { if (isDragging && mainPanel) { isDragging = false; mainPanel.style.transition = ''; } }); }; // 切换课程列表展开/收起状态 const toggleCourseList = () => { isCourseListExpanded = !isCourseListExpanded; const courseListContainer = document.getElementById('huayiCourseListContainer'); const toggleBtn = document.getElementById('huayiListToggleBtn'); courseListContainer.style.display = isCourseListExpanded ? 'block' : 'none'; toggleBtn.textContent = isCourseListExpanded ? '收起列表' : '展开列表'; localStorage.setItem('huayiCourseListExpanded', isCourseListExpanded); }; // 设置音量为0 const setVolumeToZero = () => { if (userMuteToggle) return; document.querySelectorAll('video').forEach(video => { try { video.volume = 0; } catch (e) { log(`设置video音量失败: ${e.message}`); } }); document.querySelectorAll(CONFIG.VOLUME_SLIDER_SEL).forEach(slider => { try { if (slider.tagName === 'INPUT' && ['range', 'slider'].includes(slider.type)) { slider.value = 0; slider.dispatchEvent(new Event('change', { bubbles: true })); slider.dispatchEvent(new Event('input', { bubbles: true })); } else if (slider.style.width !== undefined) { slider.style.width = '0%'; } } catch (e) { log(`操作音量滑块失败: ${e.message}`); } }); }; // 点击音量按钮尝试静音 const clickVolumeButtons = () => { if (userMuteToggle) return; let success = false; CONFIG.VOLUME_CONTROL_SELECTORS.forEach(selector => { document.querySelectorAll(selector).forEach(button => { try { const isMuted = button.getAttribute('data-muted') === 'true' || button.classList.contains('muted') || button.classList.contains('ccH5vm-mute'); if (!isMuted) { const rect = button.getBoundingClientRect(); button.dispatchEvent(new MouseEvent('click', { clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2, bubbles: true, cancelable: true })); button.setAttribute('data-muted', 'true'); success = true; } else { success = true; } } catch (e) { log(`点击音量按钮(${selector})失败: ${e.message}`); } }); }); return success; }; // 确保静音状态 const ensureMuted = () => { if (userMuteToggle || !CONFIG.AUTO_MUTE_ON_PLAY) return; if (muteAttempts >= MAX_MUTE_ATTEMPTS) { log(`已尝试${MAX_MUTE_ATTEMPTS}次静音,暂停尝试`); return; } log(`第${muteAttempts + 1}次尝试静音...`); const buttonSuccess = clickVolumeButtons(); setVolumeToZero(); try { if (document.muted !== undefined) document.muted = true; document.querySelectorAll('video').forEach(video => video.muted = true); isGlobalMuted = true; updateMuteStatus(); } catch (e) { log(`使用muted属性静音失败: ${e.message}`); } muteAttempts++; if (!buttonSuccess && muteAttempts < MAX_MUTE_ATTEMPTS) { setTimeout(ensureMuted, 2000); } }; // 切换全局静音状态 const toggleGlobalMute = () => { userMuteToggle = true; if (isGlobalMuted) { try { if (document.muted !== undefined) document.muted = false; document.querySelectorAll('video').forEach(video => { video.muted = false; if (video.volume === 0) video.volume = 1; }); CONFIG.VOLUME_CONTROL_SELECTORS.forEach(selector => { document.querySelectorAll(selector).forEach(button => { button.setAttribute('data-muted', 'false'); button.dispatchEvent(new MouseEvent('click', { bubbles: true })); }); }); isGlobalMuted = false; } catch (e) { log(`取消静音失败: ${e.message}`); } } else { try { if (document.muted !== undefined) document.muted = true; document.querySelectorAll('video').forEach(video => video.muted = true); CONFIG.VOLUME_CONTROL_SELECTORS.forEach(selector => { document.querySelectorAll(selector).forEach(button => { button.setAttribute('data-muted', 'true'); button.dispatchEvent(new MouseEvent('click', { bubbles: true })); }); }); isGlobalMuted = true; } catch (e) { log(`设置静音失败: ${e.message}`); } } updateMuteStatus(); }; // 更新静音状态显示 const updateMuteStatus = () => { const muteLabel = document.getElementById('videoMuteLabel'); if (muteLabel) { muteLabel.textContent = `全局声音:${isGlobalMuted ? '🔇 已静音' : '🔊 未静音'}`; } }; // 标记课程为异常 const markCourseAsAbnormal = (courseId) => { if (!courseId) return; const courseIndex = pendingCourses.findIndex(c => c.id === courseId); if (courseIndex === -1) return; // 只有未学习的课程才会被标记为异常 if (!pendingCourses[courseIndex].isLearned) { pendingCourses[courseIndex].isAbnormal = true; syncToLocalStorage(); updatePendingListUI(); updateNextVideo(); log(`已标记课程 ${courseId} 为异常`); } }; // 检查页面关键资源是否加载完成 const checkPageResources = () => { // 检查基本DOM元素是否存在 if (isFirstLoad && window.location.href.includes('courseware_id')) { // 播放页需要检查的资源 const videoContainer = document.querySelector('video') || document.querySelector('.bplayer-video') || document.querySelector('.ccH5Video'); const progressBar = document.querySelector('.bplayer-progress') || document.querySelector('.ccH5Progress'); // 资源未加载完成 if (!videoContainer || !progressBar) { resourceCheckAttempts++; if (resourceCheckAttempts < CONFIG.RESOURCE_CHECK_RETRY) { log(`第${resourceCheckAttempts}次检查播放页资源,尚未准备就绪`); return false; } else { log(`达到最大资源检查次数(${CONFIG.RESOURCE_CHECK_RETRY}),尝试继续`); } } } else if (isFirstLoad) { // 列表页需要检查的资源 const courseList = document.querySelector(CONFIG.COURSE_LIST_SEL) || document.querySelector(CONFIG.IFRAME_SEL); if (!courseList) { resourceCheckAttempts++; if (resourceCheckAttempts < CONFIG.RESOURCE_CHECK_RETRY) { log(`第${resourceCheckAttempts}次检查列表页资源,尚未准备就绪`); return false; } else { log(`达到最大资源检查次数(${CONFIG.RESOURCE_CHECK_RETRY}),尝试继续`); } } } // 检查页面加载时间是否足够 const elapsedTime = Date.now() - pageLoadStartTime; if (elapsedTime < CONFIG.MIN_LOADING_TIME) { log(`页面加载时间不足(${elapsedTime}ms),等待至${CONFIG.MIN_LOADING_TIME}ms`); return false; } return true; }; // 等待页面资源加载完成 const waitForResources = (callback) => { if (checkPageResources()) { log("页面资源检查通过,执行操作"); callback(); } else if (resourceCheckAttempts < CONFIG.RESOURCE_CHECK_RETRY) { setTimeout(() => waitForResources(callback), CONFIG.RESOURCE_CHECK_INTERVAL); } else { log("资源检查超时,仍尝试执行操作"); callback(); } }; // 清理所有资源 const clearAllResources = () => { log("开始清理当前页资源..."); [checkIframeTimer, updateCourseTimer, detectorTimerId, startupProgressTimerId, countdownTimerId, learnedCountdownTimerId, autoPlayIntervalId].forEach(timer => { if (timer) clearInterval(timer); }); const videoElem = document.querySelector('video'); if (videoElem) { videoElem.removeEventListener('ended', handleVideoEnded); videoElem.pause(); } isLearnedCountdownActive = false; isJumping = false; jumpAttempts = 0; log("当前页资源清理完成"); }; // 关闭当前页面 const closeCurrentPage = (retryCount = 0) => { if (!window.location.href.includes('courseware_id')) return; clearAllResources(); setTimeout(() => { try { if (window.close()) { log(`第${retryCount + 1}次关闭成功`); return; } log(`第${retryCount + 1}次直接关闭失败,尝试刷新后关闭`); window.location.reload(); setTimeout(() => { if (window.close()) log("刷新后关闭成功"); else throw new Error("刷新后关闭仍失败"); }, 1000); } catch (e) { log(`关闭失败: ${e.message}`); if (retryCount < CONFIG.PAGE_CLOSE_RETRY) { setTimeout(() => closeCurrentPage(retryCount + 1), CONFIG.PAGE_CLOSE_DELAY); } else { alert(`自动关闭失败(已重试${CONFIG.PAGE_CLOSE_RETRY + 1}次),请手动关闭`); } } }, CONFIG.PAGE_CLOSE_DELAY); }; // 创建控制面板 const createMainPanel = () => { if (document.getElementById('huayiMainPanel')) return; const storedExpanded = localStorage.getItem('huayiCourseListExpanded'); if (storedExpanded !== null) isCourseListExpanded = storedExpanded === 'true'; mainPanel = document.createElement('div'); mainPanel.id = 'huayiMainPanel'; Object.keys(CONFIG.PANEL_STYLE).forEach(key => { mainPanel.style[key] = CONFIG.PANEL_STYLE[key]; }); makePanelDraggable(); // 状态显示区域 const statusSection = document.createElement('div'); statusSection.id = 'huayiStatusSection'; statusSection.style.marginBottom = '12px'; statusSection.style.paddingBottom = '10px'; statusSection.style.borderBottom = '1px solid rgba(255,255,255,0.3)'; const statusTitle = document.createElement('h4'); statusTitle.textContent = '播放状态监控'; statusTitle.style.margin = '0 0 8px 0'; statusSection.appendChild(statusTitle); // 状态标签集合 ['videoStatusLabel', 'videoMuteLabel', 'learnedCountdownLabel', 'countdownLabel', 'videoProgressLabel', 'videoSpeedLabel', 'nextVideoLabel', 'currentCourseLabel'].forEach(id => { const label = document.createElement('p'); label.id = id; if (id === 'learnedCountdownLabel') { label.style.display = 'none'; label.style.color = '#4CAF50'; } else if (id === 'currentCourseLabel') { label.style.fontSize = '12px'; label.style.color = 'rgba(255,255,255,0.7)'; } statusSection.appendChild(label); }); // 控制按钮区域 const btnRow = document.createElement('div'); btnRow.style.display = 'flex'; btnRow.style.gap = '6px'; btnRow.style.marginTop = '8px'; btnRow.style.flexWrap = 'wrap'; const speedBtn = createStyledButton('设置倍速', 'speed'); const muteBtn = createStyledButton('切换静音', 'mute'); btnRow.appendChild(speedBtn); btnRow.appendChild(muteBtn); statusSection.appendChild(btnRow); mainPanel.appendChild(statusSection); // 课程列表区域 const pendingSection = document.createElement('div'); pendingSection.id = 'huayiPendingSection'; const pendingHeader = document.createElement('div'); pendingHeader.style.display = 'flex'; pendingHeader.style.alignItems = 'center'; const pendingTitle = document.createElement('h4'); pendingTitle.textContent = '待播放课程列表'; pendingTitle.style.margin = '0 0 8px 0'; pendingTitle.style.flex = '1'; const toggleBtn = document.createElement('button'); toggleBtn.id = 'huayiListToggleBtn'; toggleBtn.textContent = isCourseListExpanded ? '收起列表' : '展开列表'; Object.keys(CONFIG.LIST_TOGGLE_STYLE).forEach(key => { toggleBtn.style[key] = CONFIG.LIST_TOGGLE_STYLE[key]; }); toggleBtn.addEventListener('click', toggleCourseList); pendingHeader.appendChild(pendingTitle); pendingHeader.appendChild(toggleBtn); pendingSection.appendChild(pendingHeader); const courseListContainer = document.createElement('div'); courseListContainer.id = 'huayiCourseListContainer'; courseListContainer.style.display = isCourseListExpanded ? 'block' : 'none'; pendingSection.appendChild(courseListContainer); const emptyTip = document.createElement('p'); emptyTip.id = 'huayiEmptyTip'; emptyTip.textContent = '等待加载课程数据...'; courseListContainer.appendChild(emptyTip); mainPanel.appendChild(pendingSection); document.body.appendChild(mainPanel); // 绑定按钮事件 speedBtn.onclick = () => { const newSpeed = prompt('请输入播放速度 (0.5-5.0):', playbackSpeed); if (newSpeed !== null) { const speedVal = parseFloat(newSpeed); if (!isNaN(speedVal) && speedVal >= 0.5 && speedVal <= 5.0) { playbackSpeed = speedVal; setVideoSpeed(playbackSpeed); updateSpeed(playbackSpeed); speedChecked = true; clearDetectorTimer(); } else { alert('请输入有效数值 (0.5-5.0)'); } } }; muteBtn.onclick = toggleGlobalMute; // 初始化UI显示 updateStatus(getCurrentPlayStatus()); updateMuteStatus(); updateLearnedCountdown(0); updateCountdown(countdownTimeRemaining); updateProgress(0, '00:00', '00:00'); updateSpeed(playbackSpeed); updateNextVideo(); updateCurrentCourseId(); }; // 创建带样式的按钮 const createStyledButton = (text, type) => { const btn = document.createElement('button'); btn.textContent = text; Object.keys(CONFIG.BUTTON_STYLE).forEach(key => { btn.style[key] = CONFIG.BUTTON_STYLE[key]; }); const colors = CONFIG.BUTTON_COLORS[type]; if (colors) { btn.style.backgroundColor = colors.background; btn.style.color = colors.color; } btn.addEventListener('mouseover', () => { Object.keys(CONFIG.BUTTON_HOVER_STYLE).forEach(key => { btn.style[key] = CONFIG.BUTTON_HOVER_STYLE[key]; }); }); btn.addEventListener('mouseout', () => { Object.keys(CONFIG.BUTTON_HOVER_STYLE).forEach(key => { btn.style[key] = ''; }); }); return btn; }; // 更新当前课程ID显示 const updateCurrentCourseId = () => { const label = document.getElementById('currentCourseLabel'); if (label) label.textContent = `当前课程ID: ${currentCourseId || '未设置'}`; updatePendingListUI(); }; // 点击岗位课程 const clickPostCourse = () => { if (hasClickedPostCourse) return; // 首次加载增加延迟 const delay = isFirstLoad ? CONFIG.FIRST_LOAD_DELAY : 0; setTimeout(() => { const postCourseElem = document.querySelector(CONFIG.POST_COURSE_SEL); if (!postCourseElem) { setTimeout(clickPostCourse, CONFIG.CHECK_INTERVAL); return; } try { postCourseElem.click(); hasClickedPostCourse = true; startCheckIframe(); // 首次操作后标记为非首次 isFirstLoad = false; } catch (e) { log(`点击岗位课程出错: ${e.message}`); } }, delay); }; // 开始检查iframe const startCheckIframe = () => { if (checkIframeTimer) clearInterval(checkIframeTimer); checkIframeTimer = setInterval(() => { iframeDoc = getIframeDocument(); if (iframeDoc) { clearInterval(checkIframeTimer); startUpdateCourseList(); } }, CONFIG.CHECK_INTERVAL); }; // 获取iframe文档 const getIframeDocument = () => { const iframeElem = document.querySelector(CONFIG.IFRAME_SEL); if (!iframeElem) return null; try { const doc = iframeElem.contentDocument || iframeElem.contentWindow.document; return doc && doc.querySelector(CONFIG.COURSE_LIST_SEL) ? doc : null; } catch (e) { log(`访问iframe出错: ${e.message}`); return null; } }; // 解析课程项 const parseCourseItem = (itemElem) => { const courseData = { id: '', name: '', status: '', onClickParams: [], isLearned: false, isAbnormal: false, fullUrls: [], originalIndex: -1, actualPlayPath: '' }; const titleElem = itemElem.querySelector('.itemTitle'); if (titleElem) courseData.name = titleElem.textContent.trim().replace(/^\s+|\s+$/g, ''); const statusElem = itemElem.querySelector('.type p'); if (statusElem) courseData.status = statusElem.textContent.trim(); const linkElem = itemElem.querySelector('.itemText'); if (linkElem && linkElem.hasAttribute('onclick')) { const onclickStr = linkElem.getAttribute('onclick'); const paramReg = /OpenVideoPlay\(\s*'([^']*)'\s*,\s*'([^']*)'\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/; const matchRes = onclickStr.match(paramReg); if (matchRes && matchRes.length >= 5) { courseData.id = matchRes[1]; courseData.onClickParams = [ matchRes[1], matchRes[2], parseInt(matchRes[3]), parseInt(matchRes[4]) ]; CONFIG.COURSE_PLAY_PATHS.forEach(path => { courseData.fullUrls.push(`https://jcpxkh.91huayi.com/${path}?courseware_id=${matchRes[1]}`); }); courseData.actualPlayPath = onclickStr.includes('BJYCoursePlay') ? CONFIG.COURSE_PLAY_PATHS[1] : CONFIG.COURSE_PLAY_PATHS[0]; } } return courseData; }; // 筛选待播放课程 const filterPendingCourses = (allCourses) => { const validStatus = ['观看中', '未观看']; return allCourses.filter(course => validStatus.includes(course.status) && course.id && course.name && course.fullUrls.length > 0 ); }; // 更新课程列表 const updateCourseList = () => { if (!iframeDoc) return; const courseElems = iframeDoc.querySelectorAll(CONFIG.COURSE_ITEM_SEL); if (courseElems.length === 0) { pendingCourses = []; syncToLocalStorage(); updatePendingListUI(); return; } let allCourses = Array.from(courseElems).map((elem, index) => { const course = parseCourseItem(elem); course.originalIndex = index; return course; }); const storedCourses = getFromLocalStorage(); if (storedCourses.length > 0) { allCourses = allCourses.map(course => { const stored = storedCourses.find(s => s.id === course.id); return stored ? { ...course, isLearned: stored.isLearned, isAbnormal: stored.isAbnormal || false } : course; }); } pendingCourses = filterPendingCourses(allCourses) .sort((a, b) => a.originalIndex - b.originalIndex); syncToLocalStorage(); updatePendingListUI(); updateNextVideo(); log(`更新课程列表,共${pendingCourses.length}个待播放课程`); const isHomeEnv = window.location.href.includes('ExerciseHome/index'); if (isHomeEnv && !hasAutoJumped && pendingCourses.length > 0) { // 首次跳转前等待资源加载 waitForResources(() => { autoJumpToFirstValidCourse(); }); } }; // 智能跳转课程:优化首次跳转逻辑 const smartJumpToCourse = (course, retry = false) => { if (isJumping && !retry) return; isJumping = true; // 计算延迟:首次跳转或重试时增加延迟 const delay = (isFirstLoad || retry) ? CONFIG.FIRST_JUMP_DELAY : 0; // 等待延迟后执行跳转 setTimeout(() => { if (retry) { jumpAttempts++; if (jumpAttempts > MAX_JUMP_ATTEMPTS) { log(`已尝试${MAX_JUMP_ATTEMPTS}次跳转,标记课程为异常`); markCourseAsAbnormal(course.id); isJumping = false; return; } log(`第${jumpAttempts}次重试跳转至课程: ${course.name}`); } else { log(`开始跳转至课程: ${course.name}`); } try { currentCourseId = course.id; syncToLocalStorage(); updateCurrentCourseId(); const isHomeEnv = window.location.href.includes('ExerciseHome/index'); const isPlayEnv = window.location.href.includes('courseware_id'); // 列表页跳转:使用原生函数打开新页面 if (isHomeEnv && iframeDoc && iframeDoc.defaultView && typeof iframeDoc.defaultView.OpenVideoPlay === 'function' && course.onClickParams.length >= 4) { clearAllResources(); iframeDoc.defaultView.OpenVideoPlay(...course.onClickParams); setTimeout(() => { closeCurrentPage(); isJumping = false; // 首次跳转后标记为非首次 isFirstLoad = false; }, CONFIG.PAGE_CLOSE_DELAY / 2); return; } // 播放页跳转:打开新窗口后关闭原页面 const baseUrl = `https://jcpxkh.91huayi.com/${course.actualPlayPath}?courseware_id=${course.id}`; clearAllResources(); let newWindow = null; if (isPlayEnv) { newWindow = window.open(baseUrl, '_blank'); if (!newWindow) throw new Error("浏览器阻止了弹窗,请允许弹出窗口后重试"); setTimeout(() => { if (newWindow && !newWindow.closed) { log("新播放窗口已打开,准备关闭原页面"); closeCurrentPage(); } else { throw new Error("新窗口未成功打开"); } isJumping = false; // 首次跳转后标记为非首次 isFirstLoad = false; }, CONFIG.PAGE_CLOSE_DELAY); } else { window.location.href = baseUrl; isJumping = false; // 首次跳转后标记为非首次 isFirstLoad = false; } } catch (e) { log(`跳转处理出错: ${e.message}`); setTimeout(() => { smartJumpToCourse(course, true); isJumping = false; }, 2000); } }, delay); }; // 自动跳转至第一个有效课程 const autoJumpToFirstValidCourse = () => { const validCourse = pendingCourses.find(c => !c.isLearned); if (!validCourse) { log('待播放列表中无有效课程'); const emptyTip = document.getElementById('huayiEmptyTip'); if (emptyTip) emptyTip.textContent = '待播放列表中无有效课程(全为已学习)'; return; } log(`自动跳转至第一个有效课程: ${validCourse.name}`); hasAutoJumped = true; const courseItemElem = document.querySelector(`.huayiPendingCourseItem[data-course-id="${validCourse.id}"]`); if (courseItemElem) courseItemElem.click(); else smartJumpToCourse(validCourse); }; // 更新待播放列表UI const updatePendingListUI = () => { const listContainer = document.getElementById('huayiCourseListContainer'); const emptyTip = document.getElementById('huayiEmptyTip'); while (listContainer.children.length > 1) { listContainer.removeChild(listContainer.lastChild); } if (pendingCourses.length === 0) { emptyTip.textContent = '暂无待播放课程'; return; } emptyTip.style.display = 'none'; pendingCourses.forEach((course, index) => { const courseItem = document.createElement('div'); courseItem.className = 'huayiPendingCourseItem'; courseItem.dataset.courseId = course.id; courseItem.dataset.actualPath = course.actualPlayPath; Object.keys(CONFIG.COURSE_ITEM_STYLE).forEach(key => { courseItem.style[key] = CONFIG.COURSE_ITEM_STYLE[key]; }); const isCurrentPlaying = course.id === currentCourseId; if (isCurrentPlaying) { Object.keys(CONFIG.COURSE_ITEM_PLAYING_STYLE).forEach(key => { courseItem.style[key] = CONFIG.COURSE_ITEM_PLAYING_STYLE[key]; }); } let statusLabel = ''; if (course.isAbnormal) { statusLabel = `<span style="padding:2px 6px;border-radius:4px;font-size:12px;background:#f44336;color:#fff;">异常</span>`; } else if (course.isLearned) { statusLabel = `<span style="padding:2px 6px;border-radius:4px;font-size:12px;background:#9e9e9e;">已学习</span>`; } else { statusLabel = `<span style="padding:2px 6px;border-radius:4px;font-size:12px;${ course.status === '观看中' ? 'background:#ff9800' : 'background:#2196f3' }">${course.status}</span>`; } const pathLabel = course.actualPlayPath.includes('BJY') ? 'BJY路径' : '普通路径'; const playIcon = isCurrentPlaying ? '▶️ ' : ''; courseItem.innerHTML = ` <span style="display:inline-block;width:20px;text-align:center;">${playIcon}${index + 1}.</span> <span style="margin:0 8px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;${ course.isLearned ? 'text-decoration:line-through;color:#aaa;' : '' }">${course.name}</span> <span style="font-size:11px;color:#ccc;">${pathLabel}</span> ${statusLabel} `; courseItem.addEventListener('click', () => { // 点击课程时先检查资源 waitForResources(() => { smartJumpToCourse(course); }); }); courseItem.addEventListener('mouseover', () => { if (isCurrentPlaying) courseItem.style.backgroundColor = 'rgba(66, 133, 244, 0.4)'; else Object.keys(CONFIG.COURSE_ITEM_HOVER_STYLE).forEach(key => { courseItem.style[key] = CONFIG.COURSE_ITEM_HOVER_STYLE[key]; }); }); courseItem.addEventListener('mouseout', () => { if (isCurrentPlaying) { courseItem.style.backgroundColor = CONFIG.COURSE_ITEM_PLAYING_STYLE.backgroundColor; } else { Object.keys(CONFIG.COURSE_ITEM_STYLE).forEach(key => { courseItem.style[key] = CONFIG.COURSE_ITEM_STYLE[key]; }); } }); listContainer.appendChild(courseItem); }); }; // 启动课程列表更新 const startUpdateCourseList = () => { if (updateCourseTimer) clearInterval(updateCourseTimer); updateCourseList(); updateCourseTimer = setInterval(updateCourseList, CONFIG.CHECK_INTERVAL * 2); }; // 获取当前播放状态 const getCurrentPlayStatus = () => { const bplayerWrap = document.querySelector('.bplayer-wrap'); if (bplayerWrap) return bplayerWrap.classList.contains('bplayer-playing'); const videoElem = document.querySelector('video'); return videoElem ? !videoElem.paused : false; }; // 获取bplayer播放按钮 const getBplayerPlayBtn = () => document.querySelector('.bplayer-play-btn'); // 确保播放状态 const ensurePlaying = () => { const isPlaying = getCurrentPlayStatus(); if (!isPlaying) { const bplayerBtn = getBplayerPlayBtn(); const videoElem = document.querySelector('video'); if (bplayerBtn) bplayerBtn.click(); if (videoElem) videoElem.play().catch(err => log(`video播放出错: ${err.message}`)); } return getCurrentPlayStatus(); }; // 启动自动播放监控 const startAutoPlayMonitor = () => { if (autoPlayIntervalId) return; autoPlayIntervalId = setInterval(() => { const isPlaying = getCurrentPlayStatus(); updateStatus(isPlaying); if (!isPlaying) ensurePlaying(); }, CONFIG.AUTO_PLAY_RETRY_INTERVAL_MS); }; // 停止自动播放监控 const stopAutoPlayMonitor = () => { if (autoPlayIntervalId) { clearInterval(autoPlayIntervalId); autoPlayIntervalId = null; } }; // 更新播放状态显示 const updateStatus = (isPlaying) => { const label = document.getElementById('videoStatusLabel'); if (label) label.textContent = `状态: ${isPlaying ? '正在播放' : '暂停'}`; }; // 更新无进度倒计时显示 const updateCountdown = (time) => { const label = document.getElementById('countdownLabel'); if (label) { label.style.display = isLearnedCountdownActive ? 'none' : 'block'; if (!isLearnedCountdownActive) label.textContent = `无进度变化: ${time} 秒后切换`; } }; // 更新已学习倒计时显示 const updateLearnedCountdown = (time) => { const label = document.getElementById('learnedCountdownLabel'); if (label) { label.style.display = isLearnedCountdownActive ? 'block' : 'none'; if (isLearnedCountdownActive) label.textContent = `已学完,${time} 秒后切换下一个`; } }; // 更新进度显示 const updateProgress = (progress, currentTime, totalTime) => { const label = document.getElementById('videoProgressLabel'); if (label) { label.textContent = `当前进度: ${progress.toFixed(2)}% (${currentTime} / ${totalTime})`; } }; // 更新倍速显示 const updateSpeed = (speed) => { const label = document.getElementById('videoSpeedLabel'); if (label) label.textContent = `播放速度: ${speed.toFixed(1)}×`; }; // 更新下一个视频显示 const updateNextVideo = () => { const label = document.getElementById('nextVideoLabel'); if (!label) return; let nextCourse = null; if (currentCourseId && pendingCourses.length > 0) { const currentIndex = pendingCourses.findIndex(c => c.id === currentCourseId); if (currentIndex !== -1) { for (let i = currentIndex + 1; i < pendingCourses.length; i++) { if (!pendingCourses[i].isLearned) { nextCourse = pendingCourses[i]; break; } } } } if (!nextCourse) nextCourse = pendingCourses.find(c => !c.isLearned); label.textContent = nextCourse ? `下一个视频: ${nextCourse.name}` : '下一个视频: 无未学习课程'; }; // 检测并设置播放速度 const detectAndSetPlaybackSpeed = () => { if (speedChecked) return; const isBPlayer = !!document.querySelector('.bplayer-progress-bar.absolute'); const isCcH5 = !!document.querySelector('.ccH5ProgressBar'); if (isBPlayer && !isCcH5) { playbackSpeed = CONFIG.BPLAYER_SPEED; } else if (isCcH5) { playbackSpeed = CONFIG.DEFAULT_SPEED; } else return; setVideoSpeed(playbackSpeed); updateSpeed(playbackSpeed); speedChecked = true; clearDetectorTimer(); }; // 启动倍速检测定时器 const startDetectorTimer = () => { clearDetectorTimer(); if (speedChecked) return; detectorTimerId = setInterval(() => { try { detectAndSetPlaybackSpeed(); } catch (e) { log(`倍速检测出错: ${e.message}`); } }, CONFIG.DETECT_INTERVAL_MS); }; // 清除倍速检测定时器 const clearDetectorTimer = () => { if (detectorTimerId) { clearInterval(detectorTimerId); detectorTimerId = null; } }; // 设置视频倍速 const setVideoSpeed = (speed) => { const videoElem = document.querySelector('video') || document.querySelector('.bplayer-video video'); if (videoElem) { try { videoElem.playbackRate = speed; } catch (e) { log(`倍速设置失败: ${e.message}`); } } }; // 时间转秒数 const timeToSeconds = (timeStr) => { if (!timeStr) return 0; const parts = timeStr.split(':').map(Number); return parts.length === 2 ? parts[0] * 60 + parts[1] : parts[0] * 3600 + parts[1] * 60 + parts[2]; }; // 重置进度监控 const resetProgressMonitoring = () => { initialProgress = getVideoProgress(); lastProgress = initialProgress; noProgressCount = 0; countdownTimeRemaining = CONFIG.MAX_NO_PROGRESS_COUNT; updateCountdown(countdownTimeRemaining); clearCountdownTimer(); startCountdownTimer(); }; // 启动进度检查定时器 const startProgressCheckTimer = () => { resetProgressMonitoring(); startupProgressTimerId = setTimeout(() => { const currentProgress = getVideoProgress(); if (Math.abs(currentProgress - initialProgress) < CONFIG.PROGRESS_CHANGE_THRESHOLD) { log(`启动进度检查: 进度无变化,准备切换视频`); markCourseAsAbnormal(currentCourseId); playNextVideo(false); } }, CONFIG.STARTUP_PROGRESS_CHECK_TIME); }; // 清除进度检查定时器 const clearProgressCheckTimer = () => { if (startupProgressTimerId) { clearTimeout(startupProgressTimerId); startupProgressTimerId = null; } }; // 启动无进度倒计时 const startCountdownTimer = () => { clearCountdownTimer(); countdownTimerId = setInterval(() => { if (!isLearnedCountdownActive && getCurrentPlayStatus()) { countdownTimeRemaining--; updateCountdown(countdownTimeRemaining); if (countdownTimeRemaining <= 0) { log(`倒计时结束,准备切换视频`); markCourseAsAbnormal(currentCourseId); clearCountdownTimer(); playNextVideo(false); } } }, 1000); }; // 清除无进度倒计时 const clearCountdownTimer = () => { if (countdownTimerId) { clearInterval(countdownTimerId); countdownTimerId = null; } }; // 启动已学习倒计时 const startLearnedCountdown = () => { clearLearnedCountdownTimer(); isLearnedCountdownActive = true; learnedCountdownTimeRemaining = CONFIG.LEARNED_COUNTDOWN_SECONDS; updateLearnedCountdown(learnedCountdownTimeRemaining); learnedCountdownTimerId = setInterval(() => { learnedCountdownTimeRemaining--; updateLearnedCountdown(learnedCountdownTimeRemaining); if (learnedCountdownTimeRemaining <= 0) { log(`已学完倒计时结束,准备切换视频`); clearLearnedCountdownTimer(); playNextVideo(true); } }, 1000); }; // 清除已学习倒计时 const clearLearnedCountdownTimer = () => { if (learnedCountdownTimerId) { clearInterval(learnedCountdownTimerId); learnedCountdownTimerId = null; } isLearnedCountdownActive = false; updateLearnedCountdown(0); }; // 自动播放视频 const autoPlayVideo = () => { // 首次加载增加初始化延迟 const delay = isFirstLoad ? CONFIG.INITIAL_OPERATION_DELAY : 0; setTimeout(() => { // 等待资源加载完成后再执行自动播放 waitForResources(() => { const videoElem = document.querySelector('video'); const bplayerBtn = getBplayerPlayBtn(); const isPaused = !getCurrentPlayStatus(); if (isPaused) { if (bplayerBtn) bplayerBtn.click(); if (videoElem) videoElem.play().catch(err => log(`自动播放出错: ${err.message}`)); } if (CONFIG.AUTO_MUTE_ON_PLAY && !isGlobalMuted && !userMuteToggle) { muteAttempts = 0; ensureMuted(); } detectAndSetPlaybackSpeed(); updateStatus(getCurrentPlayStatus()); updateSpeed(playbackSpeed); updateNextVideo(); startCountdownTimer(); startProgressCheckTimer(); startAutoPlayMonitor(); // 首次播放后标记为非首次 isFirstLoad = false; }); }, delay); }; // 播放下一个视频 const playNextVideo = (shouldMarkAsLearned) => { clearAllResources(); stopAutoPlayMonitor(); clearCountdownTimer(); clearProgressCheckTimer(); // 无论是否异常,只要完成学习就标记为已学习 if (shouldMarkAsLearned && currentCourseId) { markCurrentCourseAsLearned(); } const unlearnedCourses = pendingCourses.filter(c => !c.isLearned); let nextCourse = null; if (unlearnedCourses.length > 0) { const currentIndex = unlearnedCourses.findIndex(c => c.id === currentCourseId); if (currentIndex !== -1 && currentIndex + 1 < unlearnedCourses.length) { nextCourse = unlearnedCourses[currentIndex + 1]; } else { nextCourse = unlearnedCourses[0]; } } if (!nextCourse) { log('没有可播放的未学习课程'); isJumping = false; return; } log(`准备跳转至下一课,将关闭当前页面`); smartJumpToCourse(nextCourse); }; // 处理视频结束事件 const handleVideoEnded = () => { log(`视频播放结束`); // 视频自然结束时,强制标记为已学习,无论是否异常 playNextVideo(true); }; // 绑定视频结束事件 const bindVideoEndedHandler = () => { const videoElem = document.querySelector('video'); if (videoElem) { videoElem.removeEventListener('ended', handleVideoEnded); videoElem.addEventListener('ended', handleVideoEnded); } }; // 绑定列表点击事件 const bindListClickHandler = () => { const listGroup = document.querySelector('div.listGroup'); if (listGroup) { listGroup.addEventListener('click', (e) => { if (e.target?.classList.contains('text')) { speedChecked = false; startDetectorTimer(); clearProgressCheckTimer(); clearLearnedCountdownTimer(); setTimeout(updateNextVideo, 500); } }); } }; // 监控视频进度 const monitorVideoProgress = () => { if (isJumping) return; const isPlaying = getCurrentPlayStatus(); if (!isPlaying) { log("状态面板显示暂停,自动恢复播放"); ensurePlaying(); updateStatus(getCurrentPlayStatus()); } if (CONFIG.AUTO_MUTE_ON_PLAY && isPlaying && !isGlobalMuted && !userMuteToggle) { ensureMuted(); } if (isLearnedCountdownActive) return; // 检查是否已学完(即使是异常课程也会检查) const currentProgress = getVideoProgress(); const isLearned = currentProgress >= 99.9; if (isLearned) { if (!isLearnedCountdownActive) { log(`检测到课程已学完(进度: ${currentProgress.toFixed(2)}%),即使标记为异常也会标记为已学习`); clearCountdownTimer(); startLearnedCountdown(); } return; } else if (isLearnedCountdownActive) { clearLearnedCountdownTimer(); } // 获取进度信息并更新UI let currentTimeElem, totalTimeElem; const durationContainer = document.querySelector('.text.duration'); if (durationContainer) { currentTimeElem = durationContainer.querySelector('.played-time'); totalTimeElem = durationContainer.querySelector('.total-time'); } if (!currentTimeElem || !totalTimeElem) { currentTimeElem = document.querySelector('.ccH5TimeCurrent'); totalTimeElem = document.querySelector('.ccH5TimeTotal'); } const videoElem = document.querySelector('video'); if (currentTimeElem && totalTimeElem) { const currentTime = currentTimeElem.textContent.trim(); const totalTime = totalTimeElem.textContent.trim(); const currentSec = timeToSeconds(currentTime); const totalSec = timeToSeconds(totalTime); if (totalSec > 0) { updateProgress(currentProgress, currentTime, totalTime); if (currentProgress >= 99.9 && !hasPlayedNext) { hasPlayedNext = true; playNextVideo(true); } if (currentProgress < 99.9) hasPlayedNext = false; } } else if (videoElem) { const cur = videoElem.currentTime || 0; const total = videoElem.duration || 0; if (total > 0) { const curStr = new Date(cur * 1000).toISOString().substr(11, 8); const totalStr = new Date(total * 1000).toISOString().substr(11, 8); updateProgress(currentProgress, curStr, totalStr); if (currentProgress >= 99.9 && !hasPlayedNext) { hasPlayedNext = true; playNextVideo(true); } if (currentProgress < 99.9) hasPlayedNext = false; } } // 检查进度变化,更新无进度计数器 if (Math.abs(currentProgress - lastProgress) >= CONFIG.PROGRESS_CHANGE_THRESHOLD) { lastProgress = currentProgress; noProgressCount = 0; countdownTimeRemaining = CONFIG.MAX_NO_PROGRESS_COUNT; updateCountdown(countdownTimeRemaining); } else { noProgressCount++; if (noProgressCount === CONFIG.PROGRESS_STALL_RETRY_SECONDS) { log(`进度已${noProgressCount}秒无变化,尝试恢复播放`); ensurePlaying(); updateStatus(getCurrentPlayStatus()); } } // 更新UI状态 updateStatus(isPlaying); if (!isPlaying) ensurePlaying(); updateSpeed(playbackSpeed); if (isPlaying && speedChecked && videoElem) setVideoSpeed(playbackSpeed); updateNextVideo(); }; // 获取视频进度 const getVideoProgress = () => { const videoElem = document.querySelector('video'); const currentTimeElem = document.querySelector('.text.duration .played-time') || document.querySelector('.ccH5TimeCurrent'); const totalTimeElem = document.querySelector('.text.duration .total-time') || document.querySelector('.ccH5TimeTotal'); if (currentTimeElem && totalTimeElem) { const currentSec = timeToSeconds(currentTimeElem.textContent.trim()); const totalSec = timeToSeconds(totalTimeElem.textContent.trim()); return totalSec > 0 ? (currentSec / totalSec) * 100 : 0; } else if (videoElem) { return videoElem.duration > 0 ? (videoElem.currentTime / videoElem.duration) * 100 : 0; } return 0; }; // 启动监控 const startMonitoring = () => { createMainPanel(); autoPlayVideo(); setInterval(monitorVideoProgress, 1000); setInterval(bindVideoEndedHandler, 2000); startDetectorTimer(); bindListClickHandler(); }; // 标记当前课程为已学习(即使是异常课程也能被标记) const markCurrentCourseAsLearned = () => { if (!currentCourseId) return; const curCourseIndex = pendingCourses.findIndex(c => c.id === currentCourseId); if (curCourseIndex === -1) return; // 无论是否为异常课程,只要调用此方法就标记为已学习 if (!pendingCourses[curCourseIndex].isLearned) { pendingCourses[curCourseIndex].isLearned = true; // 标记为已学习后清除异常标记 pendingCourses[curCourseIndex].isAbnormal = false; syncToLocalStorage(); updatePendingListUI(); updateNextVideo(); log(`标记课程 ${currentCourseId} 为已学习(即使之前标记为异常)`); } }; // 同步到本地存储 const syncToLocalStorage = () => { localStorage.setItem('huayiPendingCourses', JSON.stringify(pendingCourses)); localStorage.setItem('huayiCurrentCourseId', currentCourseId || ''); localStorage.setItem('huayiCourseListExpanded', isCourseListExpanded); }; // 从本地存储获取 const getFromLocalStorage = () => { const stored = localStorage.getItem('huayiPendingCourses'); return stored ? JSON.parse(stored) : []; }; // 初始化函数:记录页面加载开始时间 const init = () => { // 记录页面加载开始时间,用于控制最小加载时间 pageLoadStartTime = Date.now(); pendingCourses = getFromLocalStorage(); currentCourseId = localStorage.getItem('huayiCurrentCourseId') || ''; hasAutoJumped = false; resourceCheckAttempts = 0; createMainPanel(); const isHomePage = window.location.href.includes('ExerciseHome/index'); const isPlayPage = window.location.href.includes('courseware_id'); if (isHomePage) { // 首次加载列表页增加延迟 const delay = isFirstLoad ? CONFIG.FIRST_LOAD_DELAY : CONFIG.INIT_DELAY; setTimeout(() => { // 等待资源加载完成后再执行 waitForResources(() => { clickPostCourse(); startUpdateCourseList(); }); }, delay); } else if (isPlayPage) { // 首次加载播放页增加延迟 const delay = isFirstLoad ? CONFIG.FIRST_LOAD_DELAY : 3000; setTimeout(() => { // 等待资源加载完成后再执行 waitForResources(() => { startMonitoring(); isJumping = false; if (CONFIG.AUTO_MUTE_ON_PLAY && !userMuteToggle) { setTimeout(ensureMuted, 1000); } }); }, delay); const urlParams = new URLSearchParams(window.location.search); const urlCourseId = urlParams.get('courseware_id'); if (urlCourseId && !currentCourseId) { currentCourseId = urlCourseId; syncToLocalStorage(); updateCurrentCourseId(); } updatePendingListUI(); } window.addEventListener('beforeunload', clearAllResources); }; init(); })();