/** * 视频画中画UI库 * 基于 WinBox.js 实现 * 功能: * 1. 可拖拽、可调整大小的视频窗口 * 2. 根据视频宽高自动调整窗口尺寸 * 3. 支持最小化、最大化、全屏 * 4. 抖音风格视频滑动切换 * 5. 右侧操作栏、底部信息栏(自动隐藏) * * 使用方法: * 1. 引入 WinBox.js (https://rawcdn.githack.com/nextapps-de/winbox/0.2.82/dist/winbox.bundle.min.js) * 2. 引入 HLS.js (https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.6.13/hls.min.js) * 3. 使用 VideoPipLibrary.create() 创建视频窗口 * 4. 使用 player.add() 追加新视频 */ /** * WinBox.js v0.2.82 (Bundle) * Author and Copyright: Thomas Wilkerling * Licence: Apache-2.0 * Hosted by Nextapps GmbH * https://github.com/nextapps-de/winbox * */ (function(){'use strict';var e,aa=document.createElement("style");aa.innerHTML="@keyframes wb-fade-in{0%{opacity:0}to{opacity:.85}}.winbox{position:fixed;left:0;top:0;background:#0050ff;box-shadow:0 14px 28px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.22);transition:width .3s,height .3s,left .3s,top .3s;transition-timing-function:cubic-bezier(.3,1,.3,1);contain:layout size;text-align:left;touch-action:none}.wb-body,.wb-header{position:absolute;left:0}.wb-header{top:0;width:100%;height:35px;line-height:35px;color:#fff;overflow:hidden;z-index:1}.wb-body{top:35px;right:0;bottom:0;overflow:auto;-webkit-overflow-scrolling:touch;overflow-scrolling:touch;will-change:contents;background:#fff;margin-top:0!important;contain:strict;z-index:0}.wb-control *,.wb-icon{background-repeat:no-repeat}.wb-drag{height:100%;padding-left:10px;cursor:move}.wb-title{font-family:Arial,sans-serif;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wb-icon{display:none;width:20px;height:100%;margin:-1px 8px 0-3px;float:left;background-size:100%;background-position:center}.wb-e,.wb-w{width:10px;top:0}.wb-n,.wb-s{left:0;height:10px;position:absolute}.wb-n{top:-5px;right:0;cursor:n-resize;z-index:2}.wb-e{position:absolute;right:-5px;bottom:0;cursor:w-resize;z-index:2}.wb-s{bottom:-5px;right:0;cursor:n-resize;z-index:2}.wb-nw,.wb-sw,.wb-w{left:-5px}.wb-w{position:absolute;bottom:0;cursor:w-resize;z-index:2}.wb-ne,.wb-nw,.wb-sw{width:15px;height:15px;z-index:2;position:absolute}.wb-nw{top:-5px;cursor:nw-resize}.wb-ne,.wb-sw{cursor:ne-resize}.wb-ne{top:-5px;right:-5px}.wb-se,.wb-sw{bottom:-5px}.wb-se{position:absolute;right:-5px;width:15px;height:15px;cursor:nw-resize;z-index:2}.wb-control{float:right;height:100%;max-width:100%;text-align:center}.wb-control *{display:inline-block;width:30px;height:100%;max-width:100%;background-position:center;cursor:pointer}.no-close .wb-close,.no-full .wb-full,.no-header .wb-header,.no-max .wb-max,.no-min .wb-min,.no-resize .wb-body~div,.wb-body .wb-hide,.wb-show,.winbox.hide,.winbox.min .wb-body>*,.winbox.min .wb-full,.winbox.min .wb-min,.winbox.modal .wb-full,.winbox.modal .wb-max,.winbox.modal .wb-min{display:none}.winbox.max .wb-drag,.winbox.min .wb-drag{cursor:default}.wb-min{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAyIj48cGF0aCBmaWxsPSIjZmZmIiBkPSJNOCAwaDdhMSAxIDAgMCAxIDAgMkgxYTEgMSAwIDAgMSAwLTJoN3oiLz48L3N2Zz4=);background-size:14px auto;background-position:center calc(50% + 6px)}.wb-max{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9IiNmZmYiIHZpZXdCb3g9IjAgMCA5NiA5NiI+PHBhdGggZD0iTTIwIDcxLjMxMUMxNS4zNCA2OS42NyAxMiA2NS4yMyAxMiA2MFYyMGMwLTYuNjMgNS4zNy0xMiAxMi0xMmg0MGM1LjIzIDAgOS42NyAzLjM0IDExLjMxMSA4SDI0Yy0yLjIxIDAtNCAxLjc5LTQgNHY1MS4zMTF6Ii8+PHBhdGggZD0iTTkyIDc2VjM2YzAtNi42My01LjM3LTEyLTEyLTEySDQwYy02LjYzIDAtMTIgNS4zNy0xMiAxMnY0MGMwIDYuNjMgNS4zNyAxMiAxMiAxMmg0MGM2LjYzIDAgMTItNS4zNyAxMi0xMnptLTUyIDRjLTIuMjEgMC00LTEuNzktNC00VjM2YzAtMi4yMSAxLjc5LTQgNC00aDQwYzIuMjEgMCA0IDEuNzkgNCA0djQwYzAgMi4yMS0xLjc5IDQtNCA0SDQweiIvPjwvc3ZnPg==);background-size:17px auto}.wb-close{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii0xIC0xIDE4IDE4Ij48cGF0aCBmaWxsPSIjZmZmIiBkPSJtMS42MTMuMjEuMDk0LjA4M0w4IDYuNTg1IDE0LjI5My4yOTNsLjA5NC0uMDgzYTEgMSAwIDAgMSAxLjQwMyAxLjQwM2wtLjA4My4wOTRMOS40MTUgOGw2LjI5MiA2LjI5M2ExIDEgMCAwIDEtMS4zMiAxLjQ5N2wtLjA5NC0uMDgzTDggOS40MTVsLTYuMjkzIDYuMjkyLS4wOTQuMDgzQTEgMSAwIDAgMSAuMjEgMTQuMzg3bC4wODMtLjA5NEw2LjU4NSA4IC4yOTMgMS43MDdBMSAxIDAgMCAxIDEuNjEzLjIxeiIvPjwvc3ZnPg==);background-size:15px auto;background-position:5px center}.wb-full{background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2Utd2lkdGg9IjIuNSIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNOCAzSDVhMiAyIDAgMCAwLTIgMnYzbTE4IDBWNWEyIDIgMCAwIDAtMi0yaC0zbTAgMThoM2EyIDIgMCAwIDAgMi0ydi0zTTMgMTZ2M2EyIDIgMCAwIDAgMiAyaDMiLz48L3N2Zz4=);background-size:16px auto}.winbox.max .wb-body~div,.winbox.min .wb-body~div,.winbox.modal .wb-body~div,.winbox.modal .wb-drag,body.wb-lock iframe{pointer-events:none}.winbox.max{box-shadow:none}.winbox.max .wb-body{margin:0!important}.winbox iframe{position:absolute;width:100%;height:100%;border:0}body.wb-lock .winbox{will-change:left,top,width,height;transition:none}.winbox.modal:before{content:'';position:absolute;top:0;left:0;right:0;bottom:0;background:inherit;border-radius:inherit}.winbox.modal:after{content:'';position:absolute;top:-50vh;left:-50vw;right:-50vw;bottom:-50vh;background:#0d1117;animation:wb-fade-in .2s ease-out forwards;z-index:-1}.no-animation{transition:none}.no-shadow{box-shadow:none}.no-header .wb-body{top:0}.no-move:not(.min) .wb-title{pointer-events:none}.wb-body .wb-show{display:revert}"; var h=document.getElementsByTagName("head")[0];h.firstChild?h.insertBefore(aa,h.firstChild):h.appendChild(aa);var ba=document.createElement("div");ba.innerHTML="
更新时间: ${formattedDate}
` : ''}${scriptConfig.latest_notice || scriptConfig.description || '暂无公告'}
当前版本: ${currentVersion}
最新版本: ${scriptConfig.version || '未知'}
${!isLatest && scriptConfig.url ? `🔥 更新脚本` : ''}加载中...
激活码: ${activationCode}
有效期: ${data?.valid_for_days < 999 ? data.valid_for_days + '天' : '永久'}
${data?.activated_at ? `激活时间: ${new Date(data.activated_at).toLocaleString()}
` : ''} `; } else { const trialCount = await SbCLi.getTrialCount() || 0; contentEl.innerHTML = `匿名ID: ${userId}
加载激活信息失败
匿名ID: ${userId}
`; } } /** * 创建操作栏按钮 */ function createActionBar() { actionBar.innerHTML = ''; const likeBtn = document.createElement('button'); likeBtn.className = 'video-pip-action-btn'; likeBtn.innerHTML = ICONS.like; likeBtn.title = '喜欢'; likeBtn.addEventListener('click', (e) => { e.stopPropagation(); showUI(); const currentSlide = videoSlides[currentSlideIndex]; if (currentSlide) { currentSlide.isLiked = !currentSlide.isLiked; likeBtn.innerHTML = currentSlide.isLiked ? ICONS.likeFilled : ICONS.like; likeBtn.classList.toggle('liked', currentSlide.isLiked); if (config.onLike) { currentSlide.likes = currentSlide.isLiked ? currentSlide.likes + 1 : currentSlide.likes - 1; const userId = typeof SbCLi !== 'undefined' ? SbCLi.getUserId() : 'unknown'; currentSlide.like_list = currentSlide.isLiked ? [...currentSlide.like_list, userId] : currentSlide.like_list.filter(id => id !== userId); config.onLike(currentSlide); } } }); actionBar.appendChild(likeBtn); const downloadBtn = document.createElement('button'); downloadBtn.className = 'video-pip-action-btn'; downloadBtn.innerHTML = ICONS.download; downloadBtn.title = '下载'; downloadBtn.addEventListener('click', (e) => { e.stopPropagation(); showUI(); const currentSlide = videoSlides[currentSlideIndex]; //m3u8视频下载工具 if (currentSlide.video_url.includes('.m3u8')) { const downurl = `https://tools.thatwind.com/tool/m3u8downloader#m3u8=${currentSlide.video_url}&referer=${currentSlide.url}&filename=${currentSlide.content}`; window.open(downurl, '_blank'); } else if (currentSlide) { const videoUrl = currentSlide.video_url; if (videoUrl) { const a = document.createElement('a'); a.href = videoUrl; a.download = videoUrl; a.target = '_blank'; document.body.appendChild(a); a.click(); document.body.removeChild(a); } } }); actionBar.appendChild(downloadBtn); const linkBtn = document.createElement('button'); linkBtn.className = 'video-pip-action-btn'; linkBtn.innerHTML = ICONS.link; linkBtn.title = '原文链接'; linkBtn.addEventListener('click', (e) => { e.stopPropagation(); showUI(); const currentSlide = videoSlides[currentSlideIndex]; if (currentSlide?.url) { window.open(currentSlide.url, '_blank'); } }); actionBar.appendChild(linkBtn); } createActionBar(); /** * 更新信息栏 */ function updateInfoBar() { const currentSlide = videoSlides[currentSlideIndex]; if (currentSlide && config.showInfoBar) { infoTitle.textContent = currentSlide.content || ''; } } /** * 更新喜欢按钮状态 */ function updateLikeButton() { const currentSlide = videoSlides[currentSlideIndex]; const likeBtn = actionBar.querySelector('.video-pip-action-btn'); if (likeBtn && currentSlide) { likeBtn.innerHTML = currentSlide.isLiked ? ICONS.likeFilled : ICONS.like; likeBtn.classList.toggle('liked', currentSlide.isLiked); } } /** * 滑动到指定视频 * @param {number} index - 目标索引 */ function slideTo(index) { if (index < 0 || index >= videoSlides.length || isSliding) return; if (index === currentSlideIndex) return; isSliding = true; const prevIndex = currentSlideIndex; currentSlideIndex = index; const prevSlide = videoSlides[prevIndex]; const nextSlide = videoSlides[currentSlideIndex]; if (prevSlide && prevSlide.video) { prevSlide.video.pause(); } const isNext = index > prevIndex; const direction = isNext ? 1 : -1; nextSlide.element.style.transform = `translateY(${direction * 100}%)`; nextSlide.element.style.zIndex = '2'; requestAnimationFrame(() => { prevSlide.element.style.transition = 'transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; prevSlide.element.style.transform = `translateY(${-direction * 100}%)`; nextSlide.element.style.transition = 'transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)'; nextSlide.element.style.transform = 'translateY(0)'; setTimeout(() => { prevSlide.element.style.transition = ''; prevSlide.element.style.zIndex = '0'; nextSlide.element.style.transition = ''; nextSlide.element.style.zIndex = '1'; isSliding = false; currentVideo = nextSlide.video; if (nextSlide.video && config.autoplay) { nextSlide.video.play().catch(() => {}); } updateLikeButton(); updateInfoBar(); updateTitle(); showUI(); if (nextSlide.video && nextSlide.video.videoWidth && nextSlide.video.videoHeight) { currentAspectRatio = nextSlide.video.videoWidth / nextSlide.video.videoHeight; } }, 350); }); } /** * 滑动到上一个视频 */ function slidePrev() { slideTo(currentSlideIndex - 1); } /** * 滑动到下一个视频 */ function slideNext() { slideTo(currentSlideIndex + 1); } /** * 处理触摸开始 */ function handleTouchStart(e) { showUI(); // 显示信息面板或喜欢列表时不处理滑动 if (isInfoVisible || isLikesVisible) return; // 始终记录触摸起始位置 touchStartY = e.touches[0].clientY; touchStartX = e.touches[0].clientX; } /** * 处理触摸移动 */ function handleTouchMove(e) { // 显示信息面板或喜欢列表时不处理滑动 if (isInfoVisible || isLikesVisible) return; if (videoSlides.length <= 1 || isSliding) return; const deltaY = e.touches[0].clientY - touchStartY; const deltaX = e.touches[0].clientX - touchStartX; if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > 10) { e.preventDefault(); e.stopPropagation(); } } /** * 处理触摸结束 */ function handleTouchEnd(e) { // 显示信息面板或喜欢列表时不处理滑动 if (isInfoVisible || isLikesVisible) return; if (isSliding) return; const deltaY = e.changedTouches[0].clientY - touchStartY; const deltaX = e.changedTouches[0].clientX - touchStartX; const absDeltaY = Math.abs(deltaY); const absDeltaX = Math.abs(deltaX); // 只有滑动距离足够大且是垂直滑动时才切换视频 if (videoSlides.length > 1 && absDeltaY > absDeltaX && absDeltaY > 50) { if (deltaY < 0) { slideNext(); } else { slidePrev(); } } // 点击暂停/播放由视频原生控制条处理,不再自定义处理 } /** * 处理鼠标滚轮 */ function handleWheel(e) { showUI(); // 显示信息面板或喜欢列表时不阻止滚动,允许正常滚动 if (isInfoVisible || isLikesVisible) return; e.preventDefault(); e.stopPropagation(); if (videoSlides.length <= 1 || isSliding) return; if (Math.abs(e.deltaY) > 30) { if (e.deltaY > 0) { slideNext(); } else { slidePrev(); } } } /** * 处理鼠标移动(显示UI) */ function handleMouseMove() { showUI(); } container.addEventListener('touchstart', handleTouchStart, { passive: false }); container.addEventListener('touchmove', handleTouchMove, { passive: false }); container.addEventListener('touchend', handleTouchEnd, { passive: false }); container.addEventListener('wheel', handleWheel, { passive: false }); container.addEventListener('mousemove', handleMouseMove); container.addEventListener('mouseover', handleMouseMove); /** * 为视频元素添加事件监听 */ function addVideoEvents(video) { video.addEventListener('mousemove', (e) => { e.stopPropagation(); showUI(); }); video.addEventListener('mouseover', () => showUI()); // touchstart 事件让容器处理,不要阻止冒泡 } /** * 添加视频到滑动容器 * @param {Object} videoOptions - 视频配置 * @returns {number} 视频索引 */ function addVideo(videoOptions = {}) { // 加载新视频时关闭信息页和收藏页 if (isInfoVisible) { hideInfo(); } if (isLikesVisible) { hideLikes(); } const slideElement = document.createElement('div'); slideElement.className = 'video-pip-slide'; slideElement.style.zIndex = videoSlides.length === 0 ? '1' : '0'; slideElement.style.transform = videoSlides.length === 0 ? 'translateY(0)' : 'translateY(100%)'; const video = document.createElement('video'); video.playsInline = true; video.controls = true; video.loop = videoOptions.loop !== undefined ? videoOptions.loop : config.loop; video.muted = videoOptions.muted !== undefined ? videoOptions.muted : config.muted; video.volume = videoOptions.volume !== undefined ? videoOptions.volume : config.volume; video.poster = videoOptions.image_url || ''; const videoUrl = videoOptions.video_url || ''; if (videoUrl.includes('.m3u8') && typeof Hls !== 'undefined') { const hls = new Hls({ maxBufferLength: 10, maxMaxBufferLength: 30 }); hls.loadSource(videoUrl); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => { if (config.autoplay && videoSlides.length === 0) { video.play().catch(() => {}); } }); } else if (videoUrl) { video.src = videoUrl; } slideElement.appendChild(video); slider.appendChild(slideElement); addVideoEvents(video); const slideData = { element: slideElement, video: video, video_url: videoUrl, image_url: videoOptions.image_url || '', content: videoOptions.content || config.content, url: videoOptions.url || '', likes: videoOptions.likes || 0, like_list: videoOptions.like_list || [], user_id: videoOptions.user_id, isLiked: videoOptions.isLiked || false, config: { ...videoOptions } }; videoSlides.push(slideData); video.addEventListener('loadedmetadata', () => { // 加载首个视频时不调整容器大小 if (video.videoWidth && video.videoHeight) { currentAspectRatio = video.videoWidth / video.videoHeight; } if (config.autoplay && videoSlides.length === 1 && !videoUrl.includes('.m3u8')) { video.play().catch(() => {}); } }); video.addEventListener('play', () => { currentVideo = video; if (video.videoWidth && video.videoHeight) { currentAspectRatio = video.videoWidth / video.videoHeight; } // 播放时显示控制栏 showUI(); // 播放时隐藏信息栏 infoBar.classList.add('hidden'); }); video.addEventListener('pause', () => { // 暂停时不要将 currentVideo 设为 null,保持引用以便点击播放 // 暂停时显示信息栏 infoBar.classList.remove('hidden'); showUI(); // 暂停时停止自动隐藏计时器 if (autoHideTimer) { clearTimeout(autoHideTimer); autoHideTimer = null; } }); video.addEventListener('dblclick', (e) => { e.stopPropagation(); if (winbox.full) { winbox.fullscreen(false); } else { winbox.fullscreen(true); } }); if (videoSlides.length === 1) { currentVideo = video; updateInfoBar(); updateTitle(); } const newIndex = videoSlides.length - 1; if (videoSlides.length > 1) { slideTo(newIndex); } return newIndex; } /** * 根据视频宽高比调整窗口尺寸(带防抖) */ function adjustToAspectRatio(w, h) { if (isAdjustingSize || !currentAspectRatio || !winbox) return; // 最小化、最大化、全屏、显示信息面板时不调整尺寸 if (winbox.min || winbox.max || winbox.full || isInfoVisible || isLikesVisible) return; if (adjustTimer) { clearTimeout(adjustTimer); } adjustTimer = setTimeout(() => { if (!winbox) return; // 再次检查状态 if (winbox.min || winbox.max || winbox.full || isInfoVisible || isLikesVisible) return; const currentSlide = videoSlides[currentSlideIndex]; if (currentSlide?.video && currentSlide.video.videoWidth && currentSlide.video.videoHeight) { currentAspectRatio = currentSlide.video.videoWidth / currentSlide.video.videoHeight; } isAdjustingSize = true; const containerAspectRatio = w / h; let targetWidth = w; let targetHeight = h; // 计算视频在容器中实际显示的尺寸(object-fit: contain) if (containerAspectRatio > currentAspectRatio) { // 容器比视频"更扁",视频受高度限制,宽度有剩余 // 视频实际显示:宽度 = 高度 * 视频宽高比 targetWidth = h * currentAspectRatio; } else if (containerAspectRatio < currentAspectRatio) { // 容器比视频"更高",视频受宽度限制,高度有剩余 // 视频实际显示:高度 = 宽度 / 视频宽高比 targetHeight = w / currentAspectRatio; } // 只减小不增大 targetWidth = Math.min(targetWidth, w); targetHeight = Math.min(targetHeight, h); // 应用最小限制 targetWidth = Math.max(config.minWidth, targetWidth); targetHeight = Math.max(config.minHeight, targetHeight); if (Math.round(targetWidth) !== Math.round(w) || Math.round(targetHeight) !== Math.round(h)) { winbox.resize(`${Math.round(targetWidth)}px`, `${Math.round(targetHeight)}px`); // 确保窗口不超出屏幕边界 ensureWindowInBounds(); } setTimeout(() => { isAdjustingSize = false; }, 50); }, 2000); } /** * 根据视频尺寸调整窗口大小 */ function resizeToVideoSize() { // 最小化、最大化、全屏、显示信息面板、显示喜欢列表时不调整尺寸 if (!winbox || winbox.min || winbox.max || winbox.full || isInfoVisible || isLikesVisible) return; const currentSlide = videoSlides[currentSlideIndex]; if (currentSlide?.video && currentSlide.video.videoWidth && currentSlide.video.videoHeight) { currentAspectRatio = currentSlide.video.videoWidth / currentSlide.video.videoHeight; const currentWidth = winbox.width; const currentHeight = winbox.height; const containerAspectRatio = currentWidth / currentHeight; let targetWidth = currentWidth; let targetHeight = currentHeight; // 计算视频在容器中实际显示的尺寸(object-fit: contain) if (containerAspectRatio > currentAspectRatio) { // 容器比视频"更扁",视频受高度限制,宽度有剩余 // 视频实际显示:宽度 = 高度 * 视频宽高比 targetWidth = currentHeight * currentAspectRatio; } else if (containerAspectRatio < currentAspectRatio) { // 容器比视频"更高",视频受宽度限制,高度有剩余 // 视频实际显示:高度 = 宽度 / 视频宽高比 targetHeight = currentWidth / currentAspectRatio; } // 只减小不增大 targetWidth = Math.min(targetWidth, currentWidth); targetHeight = Math.min(targetHeight, currentHeight); // 应用最小限制 targetWidth = Math.max(config.minWidth, targetWidth); targetHeight = Math.max(config.minHeight, targetHeight); winbox.resize(`${Math.round(targetWidth)}px`, `${Math.round(targetHeight)}px`); } } /** * 确保窗口在屏幕边界内 */ function ensureWindowInBounds() { if (!winbox) return; const winboxEl = document.getElementById(id); if (!winboxEl) return; const rect = winboxEl.getBoundingClientRect(); const screenWidth = window.innerWidth; const screenHeight = window.innerHeight; let newX = winbox.x; let newY = winbox.y; let needMove = false; // 检查右边界 if (rect.right > screenWidth) { newX = screenWidth - rect.width - 10; needMove = true; } // 检查下边界 if (rect.bottom > screenHeight) { newY = screenHeight - rect.height - 10; needMove = true; } // 检查左边界 if (rect.left < 0) { newX = 10; needMove = true; } // 检查上边界 if (rect.top < 0) { newY = 10; needMove = true; } if (needMove) { winbox.move(`${newX}px`, `${newY}px`); } } winbox = new WinBox({ id: id, title: config.title, icon: config.icon, class: 'video-pip-window', background: config.headerBackground, x: config.x, y: config.y, width: config.width, height: config.height, minwidth: config.minWidth, minheight: config.minHeight, maxwidth: config.maxWidth, maxheight: config.maxHeight, top: config.top, right: config.right, bottom: config.bottom, left: config.left, index: initialZIndex, mount: container, onresize: function(w, h) { adjustToAspectRatio(w, h); if (config.onResize) { config.onResize(w, h); } }, onmove: function(x, y) { if (config.onMove) { config.onMove(x, y); } }, onfocus: function() { if (winbox && winbox.dom) { const newZIndex = getNextZIndex(); winbox.dom.style.zIndex = newZIndex; } if (config.onFocus) { config.onFocus(); } }, onblur: function() { if (config.onBlur) { config.onBlur(); } }, onminimize: function() { // 最小化时暂停主播放器视频 if (currentVideo && !currentVideo.paused) { currentVideo.pause(); } // 最小化时暂停喜欢列表视频 if (isLikesVisible) { // 暂停滑动视图中的视频 if (likesCurrentVideo) { likesCurrentVideo.pause(); } // 暂停网格视图中的视频 if (likesSlideElement) { const allVideos = likesSlideElement.querySelectorAll('.video-pip-likes-item video'); allVideos.forEach(v => { if (!v.paused) { v.pause(); v.closest('.video-pip-likes-item')?.classList.remove('playing'); } }); } } }, onrestore: function() { // 恢复时自动播放(如果配置了自动播放) if (currentVideo && currentVideo.paused && config.autoplay) { currentVideo.play().catch(() => {}); } } }); // 阻止事件冒泡到页面,防止页面干扰 WinBox 的拖动和调整大小 const winboxEl = document.getElementById(id); if (winboxEl) { // 设置更高的 z-index 确保在最上层 winboxEl.style.zIndex = getNextZIndex(); // 阻止事件冒泡到页面 const events = ['mousedown', 'mousemove', 'mouseup', 'touchstart', 'touchmove', 'touchend', 'pointerdown', 'pointermove', 'pointerup']; events.forEach(eventType => { winboxEl.addEventListener(eventType, (e) => { e.stopPropagation(); }, { passive: true }); }); } if (!config.showMinButton) { winbox.removeControl('wb-min'); } if (!config.showMaxButton) { winbox.removeControl('wb-max'); } if (!config.showFullscreenButton) { winbox.removeControl('wb-full'); } if (!config.showCloseButton) { winbox.removeControl('wb-close'); } if (config.showLikeButton !== false) { winbox.addControl({ index: 99, class: 'wb-likes', image: ICONS.heart, click: function(event, win) { event.stopPropagation(); showUI(); showLikes(); } }); } if (config.showSettingsButton !== false) { winbox.addControl({ index: 100, class: 'wb-settings', image: ICONS.settings, click: function(event, win) { event.stopPropagation(); showUI(); if (config.onSettings) { config.onSettings(instance); } } }); } if (config.video_url) { addVideo({ video_url: config.video_url, image_url: config.image_url, content: config.content, url: config.url, isLiked: config.isLiked, loop: config.loop, muted: config.muted, volume: config.volume }); } resetAutoHideTimer(); // 初始化时自动打开设置页面 if (config.showInfoOnStart !== false) { setTimeout(() => showInfo(), 300); } const instance = { id, winbox, container, slider, actionBar, infoBar, getVideo: (index) => { const i = index !== undefined ? index : currentSlideIndex; return videoSlides[i]?.video || null; }, getCurrentVideo: () => currentVideo, getCurrentIndex: () => currentSlideIndex, getSlideCount: () => videoSlides.length, getSlides: () => [...videoSlides], getConfig: () => ({ ...config }), add: addVideo, slideTo, slidePrev, slideNext, play: () => currentVideo?.play(), pause: () => currentVideo?.pause(), setVolume: (vol) => { if (currentVideo) currentVideo.volume = vol; }, seek: (time) => { if (currentVideo) currentVideo.currentTime = time; }, resize: (w, h) => winbox.resize(w, h), move: (x, y) => winbox.move(x, y), minimize: (state) => winbox.minimize(state), maximize: (state) => winbox.maximize(state), fullscreen: (state) => winbox.fullscreen(state), close: () => winbox.close(), setTitle: (title) => winbox.setTitle(title), setBackground: (color) => winbox.setBackground(color), focus: () => winbox.focus(), resizeToVideoSize, getAspectRatio: () => currentAspectRatio, setAspectRatio: (ratio) => { currentAspectRatio = ratio; }, isLiked: () => videoSlides[currentSlideIndex]?.isLiked || false, setLiked: (liked, index) => { const i = index !== undefined ? index : currentSlideIndex; if (videoSlides[i]) { videoSlides[i].isLiked = liked; if (i === currentSlideIndex) { updateLikeButton(); } } }, showActionBar: (show) => { config.showActionBar = show; actionBar.style.display = show ? 'flex' : 'none'; }, showInfoBar: (show) => { config.showInfoBar = show; infoBar.style.display = show ? 'block' : 'none'; }, showUI, hideUI, showInfo, hideInfo, showLikes, hideLikes, update: (options) => { if (options.title !== undefined) { config.title = options.title; updateTitle(); } if (options.autoplay !== undefined) { config.autoplay = options.autoplay; } if (options.showActionBar !== undefined) { instance.showActionBar(options.showActionBar); } if (options.showInfoBar !== undefined) { instance.showInfoBar(options.showInfoBar); } if (options.autoHideDelay !== undefined) { config.autoHideDelay = options.autoHideDelay; } }, remove: (index) => { const i = index !== undefined ? index : currentSlideIndex; if (videoSlides.length <= 1 || i < 0 || i >= videoSlides.length) return false; const slide = videoSlides[i]; if (slide.video) { slide.video.pause(); slide.video.src = ''; } slide.element.remove(); videoSlides.splice(i, 1); if (i === currentSlideIndex) { if (currentSlideIndex >= videoSlides.length) { currentSlideIndex = videoSlides.length - 1; } if (videoSlides[currentSlideIndex]) { videoSlides[currentSlideIndex].element.style.transform = 'translateY(0)'; videoSlides[currentSlideIndex].element.style.zIndex = '1'; currentVideo = videoSlides[currentSlideIndex].video; if (currentVideo && config.autoplay) { currentVideo.play().catch(() => {}); } updateLikeButton(); updateInfoBar(); updateTitle(); } } else if (i < currentSlideIndex) { currentSlideIndex--; updateTitle(); } return true; }, clear: () => { videoSlides.forEach(slide => { if (slide.video) { slide.video.pause(); slide.video.src = ''; } slide.element.remove(); }); videoSlides = []; currentSlideIndex = 0; currentVideo = null; } }; windows.set(id, instance); return instance; } /** * 获取窗口实例 */ function get(id) { return windows.get(id); } /** * 获取所有窗口 */ function getAll() { return new Map(windows); } /** * 关闭所有窗口 */ function closeAll() { windows.forEach(instance => { instance.close(); }); } return { VERSION, create, get, getAll, closeAll }; })();