HOHOJ

https://hohoj.tv/

分享者: anph (14023)发布时间: 2天前

该用户很懒,什么介绍也没有写!
二维码导入
{
    "articleStyle": 1,
    "customOrder": 0,
    "enableJs": true,
    "enabled": true,
    "enabledCookieJar": true,
    "lastUpdateTime": 0,
    "loadWithBaseUrl": true,
    "ruleArticles": "div.video-item",
    "ruleContent": "<!DOCTYPE html>\n<html lang=\"zh-Hans\">\n<head>\n<title>{{@@h5.mt-3@text}}<\/title>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no\">\n<meta name=\"referrer\" content=\"no-referrer\">\n<link href=\"https:\/\/cdn.bootcdn.net\/ajax\/libs\/plyr\/3.7.8\/plyr.css\" rel=\"stylesheet\">\n<\/head>\n<body>\n<p><\/p>\n<div class=\"video-container\">\n  <video id=\"player\" playsinline controls preload=\"auto\" poster=\"https:\/\/qyyuapi.com\/img\/noposter.png\">\n  <\/video>\n<\/div>\n<details>\n  <summary>\n    <h3><\/h3>\n  <\/summary>\n  <img>\n<\/details>\n<div class=\"all-info\">\n<div>\n    <p>🕵 片名:{{@@h5.mt-3@text}}<\/p>\n    <p>👨‍🎤 主演:{{@@div.model-name.mt-1@text}}<\/p>\n<\/div>\n<div class=\"jiekou\" style=\"\">\n<p>🎬 集数: <\/p>\n<div id=\"selected-jiekou\" onclick=\"jiekouList()\"><button data-id=\"0\"><b>无尽视频<sup>1<\/sup><\/b><\/button><\/div><div id=\"jiekou-list\" style=\"display: none;\"><\/div><\/div>\n\n{{\nlet video_url= java.getString('iframe.player@src')\nlet content = java.get(source.sourceUrl + video_url,{}).body();\nlet ss = java.getString('#my-video@src', content);\n\nlet dom = `<div class=\"jishu\" style=\"display:block;\"><p>\n<button onclick=\"jishu(this)\" data-src=\"${ss}\"><b>01<\/b><\/button>\n<\/p><\/div>`;\ndom;\n}}\n\n\n<\/div>\n\n<script src=\"https:\/\/gcore.jsdelivr.net\/npm\/hls.js@canary\"><\/script>\n<script src=\"https:\/\/cdn.bootcdn.net\/ajax\/libs\/plyr\/3.7.8\/plyr.min.js\"><\/script>\n\n<script>\n\nconst JKkey = \"{{java.md5Encode16(baseUrl.replace(\/.*\\}([^\\}\\`]+)\\`\/,'$1'))}}\";\nconst JDkey = \"{{java.md5Encode16(baseUrl.replace(\/.*\\}([^\\}\\`]+)\\`\/,'$1') + 'time')}}\";\nconst PTtime = {{\/^\\d+$\/.test('跳过片头:') ?'跳过片头:' : 0}};\nconst PWtime = {{\/^\\d+$\/.test('跳过片尾:') ? '跳过片尾:' : 0}};\nconst BSspeed = {{\/^\\d+$\/.test('长按倍速:') ? '长按倍速:' : 2}};\nconst ImageUrl = \"{{\/^http\/.test('背景图片:') ? '背景图片:' : ''}}\";\nconst Opacity1 = \"{{\/0|1|^0\\.\\d+$\/.test('图片透明度:') ? '图片透明度:' : ''}}\";\nconst Opacity2 = \"{{\/0|1|^0\\.\\d+$\/.test('按钮透明度:') ? '按钮透明度:' : ''}}\";\n\n\/\/ 获取视频URL并更新视频源\nasync function geturl() {\n    try {\n        let src = String($(\".jishu button.active\")[0].dataset.src);\n\n        \/\/ 获取页面信息\n        let fm = \"\";\n\n        \/\/ 获取视频源\n        let zyurl = [];\n        if (\/mxcontent\/.test(src)) {\n            zyurl.push({src:src,size:\"1\"});\n        } else {\n            zyurl.push({src:src,size:\"1\"});\n        }\n\n        \/\/ 更新详情封面\n        $(\"img\")[0].src = fm;\n\n        \/\/ 更新视频封面\n        $(\".video-container\")[0].style.background = `#000 url('${fm}') no-repeat center center \/ cover`;\n\n        \/\/ 返回视频源\n        let sources = zyurl;\n        return { sources: sources };\n    } catch (error) {\n        weblog(error, '错误:', true);\n        console.error(\"错误:\", error);\n        throw error;\n    }\n}\n\n\/\/ 点击集数按钮时调用的函数\nasync function jishu(item) {\n    var video = $('video')[0];\n    var wasPlaying = (video && !video.paused) || localStorage.getItem('fromEnded') === 'true';\n    if (localStorage.getItem('fromEnded') === 'true') {\n        localStorage.removeItem('fromEnded');\n    }\n    omit($('.jishu button.active'));\n    item.className = \"active\";\n    const { sources } = await geturl();\n    setTimeout(updatePadding, 100);\n    var index1 = $('#selected-jiekou button')[0].dataset.id;\n    var index2 = Array.from(item.parentNode.children).indexOf(item);\n    var Progress = {\n        index1: index1,\n        index2: index2\n    };\n    localStorage.setItem(JKkey, JSON.stringify(Progress));\n    localStorage.removeItem(JDkey);\n    initializePlayer(sources, JDkey, PTtime, PWtime, BSspeed, 1);\n    if (wasPlaying && video) {\n        const tryAutoPlay = () => {\n            if (video.readyState >= 3) {\n                video.play().catch(e => {\n                    console.log(\"自动播放被阻止:\", e);\n                    $('.plyr__control--overlaid').show();\n                });\n                video.removeEventListener('canplay', tryAutoPlay);\n            }\n        };\n        if (video.readyState >= 3) {\n            video.play().catch(e => console.log(\"立即播放失败:\", e));\n        } else {\n            video.addEventListener('canplay', tryAutoPlay);\n        }\n    }\n}\n\n\/\/ 页面加载时初始化播放器\n(async () => {\n    var m = 0,n = 0;\n    var Progress = localStorage.getItem(JKkey);\n    if (Progress) {\n        var history = JSON.parse(Progress);\n        m = history.index1;\n        n = history.index2;\n    }\n    if (m > 0) {\n        const buttonList = $('#jiekou-list')[0].querySelectorAll('button');\n        const targetButton = Array.from(buttonList).find(btn => btn.getAttribute('data-id') == m);\n        jiekou(targetButton);\n        const allButtons = $('.jishu')[m].querySelectorAll('button');\n        active(allButtons, n);\n    } else {\n        active($('.jishu button'), n);\n    }\n    const { sources } = await geturl();\n    setTimeout(updatePadding, 100);\n    localStorage.setItem('HistoryTAG', 1);\n    initializePlayer(sources, JDkey, PTtime, PWtime, BSspeed, 1);\n})();\n\n\/*************jsku************\/\n\/\/ 弹窗提示\nfunction weblog(message, title = '提示:', copy = false) {\n    \/\/ 0. 全屏状态处理(仅退出不恢复)\n    if (document.fullscreenElement) {\n        document.exitFullscreen().catch(err => {\n            console.warn('退出全屏失败:', err);\n        });\n        setTimeout(updatePadding, 100);\n    }\n\n    \/\/ 1. 保存页面原始状态\n    const previousOverflow = document.body.style.overflow;\n    const previousActiveElement = document.activeElement;\n    document.body.style.overflow = 'hidden';\n\n    \/\/ 2. 创建遮罩层\n    const overlay = document.createElement('div');\n    overlay.style.cssText = `\n        position: fixed;\n        top: 0;\n        left: 0;\n        width: 100%;\n        height: 100%;\n        background-color: rgba(0, 0, 0, 0.7);\n        z-index: 2147483647;\n        backdrop-filter: blur(3px);\n        opacity: 0;\n        transition: opacity 0.3s ease;\n        pointer-events: auto;\n    `;\n\n    \/\/ 3. 创建弹窗容器\n    const popup = document.createElement('div');\n    popup.className = 'popup';\n    popup.tabIndex = -1;\n\n    \/\/ 4. 创建标题\n    const titleElement = document.createElement('h3');\n    titleElement.textContent = title;\n    titleElement.className = 'titleElement';\n    popup.appendChild(titleElement);\n\n    \/\/ 5. 创建内容区域\n    const contentElement = document.createElement('div');\n    contentElement.textContent = message;\n    contentElement.className = 'contentElement';\n    popup.appendChild(contentElement);\n\n    \/\/ 6. 创建按钮容器\n    const buttonContainer = document.createElement('div');\n    buttonContainer.style.cssText = `\n        display: flex;\n        justify-content: flex-end;\n        gap: 12px;\n    `;\n\n    \/\/ 7. 根据copy参数决定是否显示复制按钮\n    if (copy) {\n        const copyButton = document.createElement('button');\n        copyButton.textContent = '复制文本';\n        copyButton.style.cssText = `\n            padding: 8px 18px;\n            cursor: pointer;\n            background-color: #4CAF50;\n            color: #eee;\n            border: none;\n            border-radius: 6px;\n            font-size: 14px;\n            font-weight: 500;\n            transition: all 0.2s;\n            height: 36px;\n            box-sizing: border-box;\n            line-height: 1;\n            display: inline-flex;\n            align-items: center;\n            justify-content: center;\n        `;\n\n        \/\/ 复制功能实现\n        copyButton.onclick = async () => {\n            const originalText = copyButton.textContent;\n\n            copyButton.disabled = true;\n            copyButton.style.opacity = '0.7';\n            copyButton.textContent = '复制中...';\n\n            try {\n                if (navigator.clipboard) {\n                    await navigator.clipboard.writeText(message);\n                } else {\n                    const textarea = document.createElement('textarea');\n                    textarea.value = message;\n                    textarea.style.position = 'fixed';\n                    document.body.appendChild(textarea);\n                    textarea.select();\n                    document.execCommand('copy');\n                    document.body.removeChild(textarea);\n                }\n\n                copyButton.textContent = '✓ 已复制';\n                copyButton.style.backgroundColor = '#2196F3';\n\n                setTimeout(() => {\n                    copyButton.textContent = originalText;\n                    copyButton.style.backgroundColor = '#4CAF50';\n                    copyButton.disabled = false;\n                    copyButton.style.opacity = '1';\n                }, 2000);\n            } catch (err) {\n                console.error('复制失败:', err);\n                copyButton.textContent = '复制失败';\n                setTimeout(() => {\n                    copyButton.textContent = originalText;\n                    copyButton.disabled = false;\n                    copyButton.style.opacity = '1';\n                }, 2000);\n            }\n        };\n\n        buttonContainer.appendChild(copyButton);\n    }\n\n    \/\/ 8. 创建关闭按钮\n    const closeButton = document.createElement('button');\n    closeButton.textContent = '关闭';\n    closeButton.style.cssText = `\n        padding: 8px 18px;\n        cursor: pointer;\n        background-color: #3493b6;\n        color: #eee;\n        border: none;\n        border-radius: 6px;\n        font-size: 14px;\n        font-weight: 500;\n        transition: all 0.2s;\n        height: 36px;\n        box-sizing: border-box;\n        line-height: 1;\n        display: inline-flex;\n        align-items: center;\n        justify-content: center;\n    `;\n    buttonContainer.appendChild(closeButton);\n    popup.appendChild(buttonContainer);\n\n    \/\/ 9. 关闭功能\n    const removePopup = () => {\n        popup.style.opacity = '0';\n        popup.style.transform = 'translate(-50%, -50%) scale(0.95)';\n        overlay.style.opacity = '0';\n        \n        setTimeout(() => {\n            \/\/ 移除元素\n            if (overlay.parentNode) overlay.parentNode.removeChild(overlay);\n            if (popup.parentNode) popup.parentNode.removeChild(popup);\n            \n            \/\/ 恢复页面状态\n            document.body.style.overflow = previousOverflow;\n            previousActiveElement?.focus();\n            document.removeEventListener('keydown', escHandler);\n        }, 300);\n    };\n\n    closeButton.onclick = removePopup;\n    overlay.onclick = removePopup;\n\n    \/\/ 10. 添加到页面\n    document.body.appendChild(overlay);\n    document.body.appendChild(popup);\n\n    \/\/ 触发动画\n    setTimeout(() => {\n        overlay.style.opacity = '1';\n        popup.style.opacity = '1';\n        popup.style.transform = 'translate(-50%, -50%) scale(1)';\n    }, 10);\n\n    \/\/ 11. ESC键关闭\n    const escHandler = (e) => {\n        if (e.key === 'Escape') {\n            removePopup();\n        }\n    };\n    document.addEventListener('keydown', escHandler);\n}\n\n\/\/ 选中标签\nfunction $(rule) {\n    return document.querySelectorAll(rule);\n}\n\n\/\/ 删除选中标签的class\nfunction omit(items) {\n    return Array.from(items, (item) => {\n        item.className = \"\";\n    });\n}\n\n\/\/ 选中标签的class增加active\nfunction active(items, index) {\n    items[index].className = \"active\";\n}\n\nlet isButtonListVisible = false;\n\n\/\/切换候选列表显示\nfunction jiekouList() {\n    const selectedButton = $('#selected-jiekou > button')[0];\n    const buttonList = $('#jiekou-list')[0];\n    isButtonListVisible = !isButtonListVisible;\n    if (isButtonListVisible) {\n        if (buttonList.querySelectorAll('button').length > 0) {\n            selectedButton.style.borderRadius = '0';\n            buttonList.style.display = 'block';\n        }\n        selectedButton.style.opacity = '1';\n        \/\/ 添加全局点击事件监听\n        document.addEventListener('click', function globalClickListener(e) {\n            const clickedInsideButtonList = buttonList.contains(e.target);\n            const clickedSelectedButton = $('#selected-jiekou > button')[0].contains(e.target);\n            if (!clickedInsideButtonList && !clickedSelectedButton) {\n                selectedButton.style.borderRadius = '';\n                selectedButton.style.opacity = '';\n                buttonList.style.display = 'none';\n                isButtonListVisible = false;\n                document.removeEventListener('click', globalClickListener);\n            }\n        });\n    } else {\n        selectedButton.style.borderRadius = '';\n        selectedButton.style.opacity = '';\n        buttonList.style.display = 'none';\n    }\n}\n\n\/\/切换列表接口\nfunction jiekou(clickedButton) {\n    const selectedContainer = $('#selected-jiekou')[0];\n    const selectedButton = selectedContainer.querySelector('button');\n    if (selectedButton) {\n        selectedButton.style.borderRadius = '';\n        selectedButton.style.opacity = '';\n    }\n    const buttonList = $('#jiekou-list')[0];\n    buttonList.appendChild(selectedContainer.querySelector('button'));\n    selectedContainer.appendChild(clickedButton);\n    isButtonListVisible = false;\n    buttonList.style.display = 'none';\n    const allButtons = buttonList.querySelectorAll('button');\n    allButtons.forEach(button => {\n        button.setAttribute('onclick', 'jiekou(this)');\n    });\n    const buttonsArray = Array.from(buttonList.querySelectorAll('button'));\n    buttonsArray.sort((a, b) => {\n        return parseInt(a.getAttribute('data-id')) - parseInt(b.getAttribute('data-id'));\n    });\n    buttonList.innerHTML = '';\n    buttonsArray.forEach(button => {\n        buttonList.appendChild(button);\n    });\n    JishuList(clickedButton);\n}\n\n\/\/ 切换集数列表显示\nfunction JishuList(clickedButton) {\n    const dataId = clickedButton.getAttribute('data-id');\n    const allJishuLists = $('.jishu');\n    allJishuLists.forEach(list => {\n        list.style.display = 'none';\n    });\n    allJishuLists[dataId].style.display = 'block';\n}\n\n\/\/ 保存原生的 fetch 函数\nconst originalFetch = window.fetch;\n\n\/\/ 重新定义全局的 fetch 函数\nwindow.fetch = async function(input, init = {}) {\n    const url = typeof input === 'string' ? input : input.url;\n    \n    \/\/ 准备请求选项\n    let requestOptions = {\n        method: init.method || 'GET',\n        headers: {\n            'Accept': 'text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8',\n            'Accept-Encoding': 'gzip, deflate, br',\n            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',\n            ...init.headers\n        },\n        cache: 'no-cache',\n        ...init\n    };\n\n    \/\/ 处理请求体\n    if (init.body) {\n        const body = init.body;\n        if (!requestOptions.headers['Content-Type']) {\n            requestOptions.headers['Content-Type'] = typeof body === 'object' \n                ? 'application\/json'\n                : 'application\/x-www-form-urlencoded';\n        }\n        \n        if (typeof body === 'object' && !(body instanceof FormData) && !(body instanceof URLSearchParams)) {\n            requestOptions.body = JSON.stringify(body);\n        } else if (typeof body === 'string') {\n            requestOptions.body = body.replace(\/\\+\/g, '%2B');\n        } else {\n            requestOptions.body = body;\n        }\n    }\n\n    \/\/ 如果没有请求体但存在 Content-Type 头,则删除它\n    if (!init.body && requestOptions.headers['Content-Type']) {\n        delete requestOptions.headers['Content-Type'];\n    }\n\n    \/\/ 使用降级策略\n    return await fetchWithFallback(input, init, url, requestOptions);\n};\n\n\/\/ 降级策略函数\nasync function fetchWithFallback(input, init, url, requestOptions) {\n    \/\/ 确定当前可用的方法级别\n    let methodLevel = 1; \/\/ 从最高级别开始尝试\n    \n    while (methodLevel <= 2) {\n        try {\n            switch (methodLevel) {\n                case 1: \/\/ 第一级:legado\n                    if (typeof window.run === 'function') {\n                        console.log('Using legado (level 1)');\n                        return await useWindowRun(url, requestOptions);\n                    }\n                    break;\n                    \n                case 2: \/\/ 第二级:原生 fetch\n                    console.log('Using fetch (level 2)');\n                    return await originalFetch(input, init);\n            }\n        } catch (error) {\n            console.warn(`Method level ${methodLevel} failed:`, error);\n            \/\/ 当前级别失败,尝试下一级别\n        }\n        \n        methodLevel++; \/\/ 尝试下一级别\n    }\n    \n    \/\/ 使用原生 fetch 作为最终保底\n    console.error('All method levels failed, using native fetch as final fallback');\n    return await originalFetch(input, init);\n}\n\n\/\/ 使用 legado 发送请求\nasync function useWindowRun(url, requestOptions) {\n    const origin = window.location.origin;\n    const rawText = await window.run(`\n        let response = java.connect('${\/^http\/.test(url) ? url : (origin + url)},${JSON.stringify(requestOptions)}');\n        response.code() + '#|#' + response.body();\n    `);\n    const raw = rawText.split(\"#|#\");\n    \n    \/\/ 判断状态码\n    let status = parseInt(raw[0]);\n    \n    \/\/ 状态码对应的文本\n    const statusTexts = {\n        100: 'Continue',\n        101: 'Switching Protocols',\n        102: 'Processing',\n        200: 'OK',\n        201: 'Created',\n        202: 'Accepted',\n        203: 'Non-Authoritative Information',\n        204: 'No Content',\n        205: 'Reset Content',\n        206: 'Partial Content',\n        207: 'Multi-Status',\n        208: 'Already Reported',\n        226: 'IM Used',\n        300: 'Multiple Choices',\n        301: 'Moved Permanently',\n        302: 'Found',\n        303: 'See Other',\n        304: 'Not Modified',\n        305: 'Use Proxy',\n        307: 'Temporary Redirect',\n        308: 'Permanent Redirect',\n        400: 'Bad Request',\n        401: 'Unauthorized',\n        402: 'Payment Required',\n        403: 'Forbidden',\n        404: 'Not Found',\n        405: 'Method Not Allowed',\n        406: 'Not Acceptable',\n        407: 'Proxy Authentication Required',\n        408: 'Request Timeout',\n        409: 'Conflict',\n        410: 'Gone',\n        411: 'Length Required',\n        412: 'Precondition Failed',\n        413: 'Payload Too Large',\n        414: 'URI Too Long',\n        415: 'Unsupported Media Type',\n        416: 'Range Not Satisfiable',\n        417: 'Expectation Failed',\n        418: 'I\\'m a teapot',\n        421: 'Misdirected Request',\n        422: 'Unprocessable Entity',\n        423: 'Locked',\n        424: 'Failed Dependency',\n        425: 'Too Early',\n        426: 'Upgrade Required',\n        428: 'Precondition Required',\n        429: 'Too Many Requests',\n        431: 'Request Header Fields Too Large',\n        451: 'Unavailable For Legal Reasons',\n        500: 'Internal Server Error',\n        501: 'Not Implemented',\n        502: 'Bad Gateway',\n        503: 'Service Unavailable',\n        504: 'Gateway Timeout',\n        505: 'HTTP Version Not Supported',\n        506: 'Variant Also Negotiates',\n        507: 'Insufficient Storage',\n        508: 'Loop Detected',\n        510: 'Not Extended',\n        511: 'Network Authentication Required'\n    };\n    \n    \/\/ 提取响应体\n    const responseBody = raw.length > 1 ? raw[1] : '';\n    \n    \/\/ 判断Content-Type\n    let contentType = 'text\/plain;charset=utf-8';\n    \n    \/\/ 先检查URL扩展名\n    const urlLower = url.toLowerCase();\n    if (urlLower.includes('.ts') || urlLower.includes('.m3u8') || urlLower.includes('.mpeg')) {\n        contentType = 'video\/mp2t';\n    } else if (urlLower.includes('.mp4') || urlLower.includes('.m4v')) {\n        contentType = 'video\/mp4';\n    } else if (urlLower.includes('.webm')) {\n        contentType = 'video\/webm';\n    } else if (urlLower.includes('.avi')) {\n        contentType = 'video\/x-msvideo';\n    } else if (urlLower.includes('.mov')) {\n        contentType = 'video\/quicktime';\n    } else if (urlLower.includes('.flv')) {\n        contentType = 'video\/x-flv';\n    } else if (urlLower.includes('.mkv')) {\n        contentType = 'video\/x-matroska';\n    } else if (urlLower.includes('.jpg') || urlLower.includes('.jpeg')) {\n        contentType = 'image\/jpeg';\n    } else if (urlLower.includes('.png')) {\n        contentType = 'image\/png';\n    } else if (urlLower.includes('.gif')) {\n        contentType = 'image\/gif';\n    } else if (urlLower.includes('.svg')) {\n        contentType = 'image\/svg+xml';\n    } else if (urlLower.includes('.webp')) {\n        contentType = 'image\/webp';\n    } else if (urlLower.includes('.json')) {\n        contentType = 'application\/json;charset=utf-8';\n    } else if (urlLower.includes('.xml')) {\n        contentType = 'application\/xml;charset=utf-8';\n    } else if (urlLower.includes('.html') || urlLower.includes('.htm')) {\n        contentType = 'text\/html;charset=utf-8';\n    } else if (urlLower.includes('.css')) {\n        contentType = 'text\/css;charset=utf-8';\n    } else if (urlLower.includes('.js')) {\n        contentType = 'application\/javascript;charset=utf-8';\n    } else if (urlLower.includes('.pdf')) {\n        contentType = 'application\/pdf';\n    } else if (urlLower.includes('.zip')) {\n        contentType = 'application\/zip';\n    } else if (urlLower.includes('.rar')) {\n        contentType = 'application\/x-rar-compressed';\n    } else if (urlLower.includes('.7z')) {\n        contentType = 'application\/x-7z-compressed';\n    } else if (urlLower.includes('.tar')) {\n        contentType = 'application\/x-tar';\n    } else if (urlLower.includes('.gz')) {\n        contentType = 'application\/gzip';\n    } else {\n        \/\/ 如果没有通过URL判断出来,尝试通过内容判断\n        if (responseBody && responseBody.length > 0) {\n            \/\/ 尝试判断是否为JSON\n            try {\n                \/\/ 检查是否以{或[开头\n                const trimmed = responseBody.trim();\n                if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n                    JSON.parse(responseBody);\n                    contentType = 'application\/json;charset=utf-8';\n                }\n            } catch (e) {\n                \/\/ 不是JSON,继续检查\n            }\n            \n            \/\/ 检查是否为HTML\n            if (responseBody.includes('<!DOCTYPE') || \n                responseBody.includes('<html') || \n                responseBody.includes('<head>') || \n                responseBody.includes('<body>')) {\n                contentType = 'text\/html;charset=utf-8';\n            }\n            \n            \/\/ 检查是否为CSS\n            else if (responseBody.includes('{') && responseBody.includes('}') && \n                    (responseBody.includes('color:') || \n                     responseBody.includes('font-size:') || \n                     responseBody.includes('background:'))) {\n                contentType = 'text\/css;charset=utf-8';\n            }\n            \n            \/\/ 检查是否为JavaScript\n            else if (responseBody.includes('function') || \n                    responseBody.includes('var ') || \n                    responseBody.includes('const ') || \n                    responseBody.includes('let ') || \n                    responseBody.includes('console.log')) {\n                contentType = 'application\/javascript;charset=utf-8';\n            }\n            \n            \/\/ 检查是否包含大量非ASCII字符或控制字符(可能是二进制数据)\n            else if (responseBody.length > 100) {\n                let binaryCount = 0;\n                for (let i = 0; i < Math.min(responseBody.length, 1000); i++) {\n                    const charCode = responseBody.charCodeAt(i);\n                    \/\/ 控制字符(除了常见的空白字符)或非ASCII字符\n                    if ((charCode < 32 && charCode !== 9 && charCode !== 10 && charCode !== 13) || charCode > 127) {\n                        binaryCount++;\n                    }\n                }\n                \n                \/\/ 如果超过5%的字符是二进制特征,认为是二进制数据\n                if (binaryCount > Math.min(responseBody.length, 1000) * 0.05) {\n                    contentType = 'application\/octet-stream';\n                }\n            }\n        }\n    }\n    \n    \/\/ 创建并返回Response对象\n    return new Response(responseBody, {\n        status: status,\n        statusText: statusTexts[status] || 'Unknown Status',\n        headers: new Headers({\n            'Content-Type': contentType\n        })\n    });\n}\n\n\/\/ fetchRequest 函数\nasync function fetchRequest(url, headers, body = null) {\n    try {\n        const init = {\n            headers: headers\n        };\n        \n        if (body) {\n            init.method = 'POST';\n            init.body = body;\n        }\n\n        const response = await fetch(url, init);\n        \n        if (!response.ok) {\n            throw new Error(`HTTP error! Status: ${response.status}`);\n        }\n\n        const rawText = await response.text();\n\n        \/\/ 尝试解析 JSON\n        try {\n            const jsonData = JSON.parse(rawText);\n            return jsonData;\n        } catch (e) {\n            \/\/ 如果不是 JSON,返回原始文本\n            return rawText;\n        }\n    } catch (error) {\n        weblog(error, '请求失败:', true);\n        return null;\n    }\n}\n\n\/\/ 全局时间管理器\nconst TimeManager = {\n    currentVideoUrl: '',\n    headtime: 0,\n    endtime: 0,\n    listeners: [],\n\n    init(videoUrl) {\n        this.currentVideoUrl = videoUrl;\n        this._loadFromStorage();\n        this._setupListeners();\n    },\n\n    _loadFromStorage() {\n        const head = localStorage.getItem('head' + this.currentVideoUrl);\n        const end = localStorage.getItem('end' + this.currentVideoUrl);\n        this.headtime = head ? parseFloat(head) : 0;\n        this.endtime = end ? parseFloat(end) : 0;\n        this._notifyAll();\n    },\n\n    _setupListeners() {\n        window.addEventListener('storage', (e) => {\n            if (e.key === 'head' + this.currentVideoUrl) {\n                this.headtime = e.newValue ? parseFloat(e.newValue) : 0;\n                this._notify('head', this.headtime);\n            }\n            if (e.key === 'end' + this.currentVideoUrl) {\n                this.endtime = e.newValue ? parseFloat(e.newValue) : 0;\n                this._notify('end', this.endtime);\n            }\n        });\n    },\n\n    setHeadTime(time) {\n        this.headtime = time;\n        localStorage.setItem('head' + this.currentVideoUrl, time);\n        this._notify('head', time);\n    },\n\n    setEndTime(time) {\n        this.endtime = time;\n        localStorage.setItem('end' + this.currentVideoUrl, time);\n        this._notify('end', time);\n    },\n\n    clearMarks() {\n        localStorage.removeItem('head' + this.currentVideoUrl);\n        localStorage.removeItem('end' + this.currentVideoUrl);\n        this.headtime = 0;\n        this.endtime = 0;\n        this._notifyAll();\n    },\n\n    addListener(callback) {\n        this.listeners.push(callback);\n    },\n\n    _notify(type, value) {\n        this.listeners.forEach(cb => cb({ type, value }));\n    },\n\n    _notifyAll() {\n        this._notify('head', this.headtime);\n        this._notify('end', this.endtime);\n    }\n};\n\n\/\/ 初始设置\nlet player = null;\nlet timeManager = null;\nlet currentHls = null;\nlet isSwitching = false;\n\n\/\/ 初始化播放器\nfunction initializePlayer(sources, currentVideoUrl, defaultHeadtime, defaultEndtime, speed, autonext) {\n    const video = $('video')[0];\n    video.style.height = '56.25vw';\n    const qualityOptions = sources.map(source => parseInt(source.size));\n\n    \/\/ 修改图片和透明度\n    updateBackground(\n        typeof ImageUrl !== 'undefined' ? ImageUrl : undefined,\n        typeof Opacity1 !== 'undefined' ? Opacity1 : undefined, \n        typeof Opacity2 !== 'undefined' ? Opacity2 : undefined\n    );\n\n    \/\/ 初始化时间管理器\n    if (!timeManager || timeManager.currentVideoUrl !== currentVideoUrl) {\n        timeManager = Object.create(TimeManager);\n        timeManager.init(currentVideoUrl);\n        \n        \/\/ 设置默认值(如果localStorage中没有)\n        if (!localStorage.getItem('head' + currentVideoUrl) && defaultHeadtime) {\n            timeManager.setHeadTime(defaultHeadtime);\n        }\n        if (!localStorage.getItem('end' + currentVideoUrl) && defaultEndtime) {\n            timeManager.setEndTime(defaultEndtime);\n        }\n    }\n\n    \/\/ 如果播放器已存在,则只更新视频源\n    if (player) {\n        \/\/ 获取当前画质\n        let currentQuality = qualityOptions[0];\n        if (player.storage && player.storage.get) {\n            const savedQuality = player.storage.get('quality');\n            if (savedQuality && qualityOptions.includes(parseInt(savedQuality))) {\n                currentQuality = parseInt(savedQuality);\n            }\n        }\n        if (!currentQuality) {\n            currentQuality = Math.max(...qualityOptions);\n        }\n        \n        \/\/ 加载当前画质的视频源\n        const selectedSource = sources.find(source => source.size === currentQuality.toString()) || sources[0];\n        const savedProgress = localStorage.getItem(currentVideoUrl);\n        const progress = (savedProgress && savedProgress > timeManager.headtime) ? savedProgress : timeManager.headtime;\n        \n        \/\/ 更新画质配置\n        player.config.quality.options = qualityOptions;\n        player.config.quality.default = currentQuality;\n        player.config.quality.onChange = (newQuality) => {\n            changeVideoQuality(newQuality, sources);\n        };\n        \n        \/\/ 更新主菜单的画质显示\n        mainMenuQuality(currentQuality)\n        \n        \/\/ 更新画质菜单选项\n        updateQualityOptions(qualityOptions, currentQuality, sources);\n        \n        \/\/ 更新p标签显示当前源\n        $(\"body>p\")[0].innerHTML = `\n          <div style=\"display: flex; align-items: center; justify-content: space-between; width: 100%;\">\n            <a href=\"legadovideo:\/\/${encodeURIComponent(selectedSource.src)}\">\n              ${selectedSource.src}\n            <\/a>\n            <button \n              onclick=\"navigator.clipboard.writeText('${selectedSource.src.replace(\/'\/g, \"\\\\'\")}')\n                .then(() => weblog('链接已复制到剪贴板'))\n                .catch(() => weblog('复制链接失败'))\"\n            >\n              复制\n            <\/button>\n          <\/div>\n        `;\n        \n        setTimeout(() => (isSwitching = false), 2000);\n        \n        if (Hls.isSupported() && \/m3u8|hls\/.test(selectedSource.src)) {\n            initializeHlsPlayer(selectedSource.src, selectedSource.headersOrReferer, video, progress);\n        } else {\n            initializeRegularPlayer(selectedSource.src, selectedSource.headersOrReferer, video, progress);\n        }\n        \n        return player;\n    }\n\n    \/\/ 创建新的播放器实例\n    player = new Plyr(video, {\n        controls: [\n            'play-large',   \/\/ 大播放按钮\n            'rewind',       \/\/ 倒退\n            'play',         \/\/ 播放\n            'fast-forward', \/\/ 快进\n            'progress',     \/\/ 进度条\n            'current-time', \/\/ 当前时间\n            'duration',     \/\/ 总时长\n            'mute',         \/\/ 静音\n            'volume',       \/\/ 音量\n            'captions',     \/\/ 字幕\n            'settings',     \/\/ 设置\n            'pip',          \/\/ 画中画\n            'airplay',      \/\/ Airplay\n            'fullscreen'    \/\/ 全屏\n        ],\n        settings: ['quality', 'speed'],\n        quality: {\n            default: qualityOptions[0],\n            options: qualityOptions,\n            forced: true,\n            onChange: (newQuality) => {\n                changeVideoQuality(newQuality, sources);\n            }\n        },\n        fullscreen: {\n            enabled: true,\n            fallback: true,\n            iosNative: true,\n            container: null,\n        },\n        speed: {\n            selected: 1, \/\/ 设置默认播放倍数\n            options: [8,5,4,3,2.5, 2, 1.5, 1, 0.5, 0.25],\n        },\n        i18n: {\n            restart: '重新开始',\n            rewind: '倒退 {seektime} 秒',\n            play: '播放',\n            pause: '暂停',\n            fastForward: '快进 {seektime} 秒',\n            seek: '进度',\n            seekLabel: '{currentTime} \/ {duration}',\n            played: '播放',\n            buffered: '缓冲',\n            currentTime: '当前时间',\n            duration: '持续时间',\n            volume: '音量',\n            mute: '静音',\n            unmute: '取消静音',\n            enableCaptions: '启用字幕',\n            disableCaptions: '禁用字幕',\n            enterFullscreen: '进入全屏',\n            exitFullscreen: '退出全屏',\n            frameTitle: '播放器',\n            captions: '字幕',\n            settings: '设置',\n            speed: '速度',\n            normal: '正常',\n            quality: '画质',\n            qualityLabel: {\n                0: '自动',\n            },\n            pip: '画中画',\n            loop: '循环',\n            start: '开始',\n            end: '结束',\n            all: '全部',\n            reset: '重置',\n            disabled: '禁用',\n            advertisement: '广告'\n        },\n        keyboard: {\n            focused: true,\n            global: true,\n        },\n        tooltips: {\n            controls: true,\n            seek: true\n        },\n        captions: {\n            active: true,\n            update: true,\n            language: 'auto',\n        },\n    });\n\n    player.on('ready', () => {\n        video.style.visibility = 'visible';\n\n        \/\/ 添加显示集数\n        addEpisodeBarToPlayer();\n\n        \/\/ 添加跳过菜单项\n        addSkipMenuItems(currentVideoUrl, timeManager);\n\n        \/\/ 初始化播放器方向控制功能\n        initPlayerOrientationControl(player);\n\n        \/\/ 设置进度记录\n        progressRecording(currentVideoUrl, video);\n\n        \/\/ 设置自动下一集监听\n        autoNextListener(currentVideoUrl, autonext, video);\n\n        \/\/ 添加上下集按钮\n        addEpisodeButtons(currentVideoUrl, autonext);\n\n        \/\/ 设置手势控制\n        gestureControls(player, video, speed);\n\n        \/\/ 设置全屏和按钮显示处理\n        fullscreenControls(autonext, video);\n\n        \/\/ 初始化处理全屏状态\n        updateFullscreen(autonext, video);\n\n        \/\/ 播放开始自动改为黑色背景\n        player.on('play', () => {\n            $(\".video-container\")[0].style.background = '#000';\n        });\n\n        \/\/ 进度大于0时自动改为黑色背景\n        player.on('timeupdate', () => {\n            if (video.currentTime > 0) $(\".video-container\")[0].style.background = '#000';\n        });\n\n        \/\/ 获取保存的播放进度\n        const savedProgress = localStorage.getItem(currentVideoUrl);\n        const progress = (savedProgress && savedProgress > timeManager.headtime) ? parseFloat(savedProgress) : timeManager.headtime;\n        if (!isNaN(progress) && progress > 0) {\n            video.currentTime = progress;\n        }\n\n        \/\/ 调用切换画质函数加载默认画质\n        changeVideoQuality(qualityOptions[0], sources);\n    });\n\n    return player;\n}\n\n\/\/ 修改图片和透明度\nfunction updateBackground(imageUrl, opacity1, opacity2) {\n    if (!imageUrl || imageUrl === '') imageUrl = 'https:\/\/bg.qyyuapi.com\/img\/background.jpg';\n    if (!opacity1 || opacity1 === '') opacity1 = 0.2;\n    if (!opacity2 || opacity2 === '') opacity2 = 0.6;\n    document.body.style.setProperty('--bg-image', `url('${imageUrl}')`);\n    document.body.style.setProperty('--bg-opacity1', opacity1);\n    document.body.style.setProperty('--bg-opacity2', opacity2);\n}\n\n\/\/ 更新画质菜单选项\nfunction updateQualityOptions(qualityOptions, currentQuality, sources) {\n    if (!player || !player.elements || !player.elements.settings) return;\n    \n    try {\n        const settings = player.elements.settings;\n        const qualityPanel = settings.panels.quality;\n        if (!qualityPanel) return;\n        const menuContainer = qualityPanel.querySelector('[role=\"menu\"]');\n        if (!menuContainer) return;\n        menuContainer.innerHTML = '';\n        qualityOptions.forEach(quality => {\n            const isSelected = quality === currentQuality;\n            const button = createQualityButton(quality, isSelected, sources);\n            menuContainer.appendChild(button);\n        });\n    } catch (error) {\n        console.error('更新画质菜单选项失败:', error);\n    }\n}\n\n\/\/ 创建画质按钮\nfunction createQualityButton(quality, isSelected, sources) {\n    const button = document.createElement('button');\n    button.type = 'button';\n    button.className = 'plyr__control';\n    button.setAttribute('role', 'menuitemradio');\n    button.setAttribute('aria-checked', isSelected ? 'true' : 'false');\n    button.setAttribute('data-plyr', 'quality');\n    button.value = quality;\n    \n    \/\/ 根据画质生成对应的按钮\n    const badge = getQualityBadge(quality);\n    button.innerHTML = `\n        <span>${quality}p${badge ? `<span class=\"plyr__menu__value\"><span class=\"plyr__badge\">${badge}<\/span><\/span>` : '<\/span>'}\n    `;\n    \n    \/\/ 绑定点击事件\n    button.addEventListener('click', function(e) {\n        e.preventDefault();\n        e.stopPropagation();\n        \n        const quality = parseInt(this.value);\n        \n        \/\/ 1. 触发画质变更事件\n        if (player.quality) {\n            player.quality.current = quality;\n            if (player.quality.set) {\n                player.quality.set(quality);\n            }\n            player.emit('qualitychange', quality);\n        }\n        \n        \/\/ 2. 更新配置\n        player.config.quality.current = quality;\n        \n        \/\/ 3. 保存到Plyr存储\n        if (player.storage && player.storage.set) {\n            player.storage.set({ quality: quality });\n        }\n        \n        \/\/ 4. 更新画质菜单选中状态\n        qualitySelection(quality);\n        \n        \/\/ 5. 更新主菜单的画质显示\n        mainMenuQuality(quality);\n        \n        \/\/ 6. 调用画质切换函数\n        changeVideoQuality(quality, sources);\n        \n        \/\/ 7. 返回主菜单\n        backToMainMenu();\n    });\n    \n    \/\/ 设置初始选中状态\n    if (isSelected) {\n        button.classList.add('plyr__control--selected');\n        const checkSpan = button.querySelector('.plyr__control--checked');\n        if (checkSpan) {\n            checkSpan.style.display = 'block';\n            checkSpan.innerHTML = '✓';\n        }\n    }\n    \n    return button;\n}\n\n\/\/ 更新画质菜单选中状态\nfunction qualitySelection(selectedQuality) {\n    if (!player || !player.elements.settings) return;\n    \n    const settings = player.elements.settings;\n    const qualityPanel = settings.panels.quality;\n    if (!qualityPanel) return;\n    \n    const menuContainer = qualityPanel.querySelector('[role=\"menu\"]');\n    if (!menuContainer) return;\n    \n    const allButtons = menuContainer.querySelectorAll('[data-plyr=\"quality\"]');\n    allButtons.forEach(btn => {\n        const quality = parseInt(btn.value);\n        const isSelected = quality === selectedQuality;\n        \n        btn.setAttribute('aria-checked', isSelected ? 'true' : 'false');\n        \n        if (isSelected) {\n            btn.classList.add('plyr__control--selected');\n            const checkSpan = btn.querySelector('.plyr__control--checked');\n            if (checkSpan) {\n                checkSpan.style.display = 'block';\n                checkSpan.innerHTML = '✓';\n                checkSpan.style.color = 'var(--plyr-control-toggle-checked-background, #00b3ff)';\n            }\n        } else {\n            btn.classList.remove('plyr__control--selected');\n            const checkSpan = btn.querySelector('.plyr__control--checked');\n            if (checkSpan) {\n                checkSpan.style.display = 'none';\n            }\n        }\n    });\n}\n\n\/\/ 在home菜单中查找画质按钮\nfunction findQualityMenu(homeMenu) {\n    const allButtons = homeMenu.querySelectorAll('[data-plyr=\"settings\"]');\n    \n    for (let button of allButtons) {\n        const buttonText = button.textContent || button.innerText;\n        \n        if (buttonText.includes('画质')) {\n            return button;\n        }\n    }\n    \n    console.log('未找到包含关键词的按钮');\n    return null;\n}\n\n\/\/ 更新主菜单的画质显示\nfunction mainMenuQuality(quality) {\n    try {\n        \/\/ 查找设置弹出层容器\n        const menuContainer = document.querySelector('.plyr__menu__container');\n        if (!menuContainer) {\n            console.log('未找到菜单容器');\n            return;\n        }\n        \n        \/\/ 查找主菜单(包含\"-home\"的ID)\n        const homeMenu = menuContainer.querySelector('[id*=\"-home\"]');\n        if (!homeMenu) {\n            console.log('未找到主菜单');\n            return;\n        }\n        \n        \/\/ 在主菜单中查找画质按钮\n        const qualityButton = findQualityMenu(homeMenu);\n        if (!qualityButton) {\n            console.log('未找到画质按钮');\n            return;\n        }\n        \n        \/\/ 更新画质显示\n        const valueSpan = qualityButton.querySelector('.plyr__menu__value');\n        if (valueSpan) {\n            valueSpan.innerHTML = `${quality}p`;\n        } else {\n            console.log('未找到valueSpan');\n        }\n        \n    } catch (error) {\n        console.error('更新主菜单显示失败:', error);\n    }\n}\n\n\/\/ 返回主菜单\nfunction backToMainMenu() {\n    try {\n        \/\/ 查找设置弹出层容器\n        const menuContainer = document.querySelector('.plyr__menu__container');\n        if (!menuContainer) {\n            console.log('未找到菜单容器');\n            return;\n        }\n        \n        \/\/ 更改home菜单和quality菜单显示\n        const homeMenu = menuContainer.querySelector('[id*=\"-home\"]');\n        const qualityMenu = menuContainer.querySelector('[id*=\"-quality\"]');\n        \n        if (homeMenu && qualityMenu) {\n            homeMenu.removeAttribute('hidden');\n            qualityMenu.setAttribute('hidden', '');\n            \n            return;\n        }\n        \n        console.log('未找到需要的菜单');\n        \n    } catch (error) {\n        console.error('返回主菜单失败:', error);\n    }\n}\n\n\/\/ 根据画质返回对应的徽章文本\nfunction getQualityBadge(quality) {\n    if (quality >= 2160) return '4K';\n    if (quality >= 1440) return '2K';\n    if (quality >= 1080) return 'FHD';\n    if (quality >= 720) return 'HD';\n    if (quality >= 480) return 'SD';\n    if (quality >= 360) return 'LD';\n    if (quality >= 240) return 'ULD';\n    return '';\n}\n\n\/\/ 设置进度记录\nfunction progressRecording(currentVideoUrl, video) {\n    const progressInterval = setInterval(() => {\n        const currentTime = video.currentTime;\n        localStorage.setItem(currentVideoUrl, currentTime.toString());\n        localStorage.setItem('HistoryTIME', Date.now());\n    }, 5000);\n}\n\n\/\/ 设置自动下一集监听\nfunction autoNextListener(currentVideoUrl, autonext, video) {\n    player.on('timeupdate', () => {\n        if (!autonext || video.paused || timeManager.endtime === 0 || video.duration <= 0) return;\n        if ((video.duration - video.currentTime) < timeManager.endtime) {\n            const randomDelay = Math.floor(Math.random() * 200) + 100;\n            setTimeout(() => {\n                if (isSwitching) return;\n                switchToNextEpisode(currentVideoUrl, autonext);\n            }, randomDelay);\n        }\n    });\n\n    player.on('ended', () => {\n        if (isSwitching || !autonext || video.duration <= 0) return;\n        const randomDelay = Math.floor(Math.random() * 200) + 100;\n        setTimeout(() => {\n            if (isSwitching) return;\n            localStorage.setItem('fromEnded', 'true');\n            switchToNextEpisode(currentVideoUrl, autonext);\n        }, randomDelay);\n    });\n}\n\n\/\/ 添加上下集按钮\nfunction addEpisodeButtons(currentVideoUrl, autonext) {\n    if (autonext) {\n        const style = document.createElement('style');\n        style.textContent = `\n            .plyr__control--episode-prev,\n            .plyr__control--episode-next {\n                display: none;\n                margin: 1px;\n            }\n            .plyr__controls [data-plyr=\"rewind\"],\n            .plyr__controls [data-plyr=\"fast-forward\"] {\n                margin: 1px;\n            }\n        `;\n        document.head.appendChild(style);\n\n        const controlsContainer = document.querySelector('.plyr__controls');\n        if (controlsContainer) {\n            \/\/ 获取参考按钮\n            const rewindBtn = controlsContainer.querySelector('[data-plyr=\"rewind\"]');\n            const fastForwardBtn = controlsContainer.querySelector('[data-plyr=\"fast-forward\"]');\n\n            \/\/ 创建上一集按钮(放在后退前)\n            if (rewindBtn && !document.querySelector('.plyr__control--episode-prev')) {\n                const prevBtn = document.createElement('button');\n                prevBtn.className = 'plyr__control plyr__control--episode-prev';\n                prevBtn.innerHTML = `\n                    <svg aria-hidden=\"true\" focusable=\"false\" width=\"18\" height=\"18\">\n                        <use xlink:href=\"#plyr-prev-episode\"><\/use>\n                    <\/svg>\n                    <span class=\"plyr__tooltip\">上一集<\/span>\n                `;\n                prevBtn.onclick = (e) => {\n                    e.preventDefault();\n                    switchToPrevEpisode(currentVideoUrl, autonext);\n                };\n                rewindBtn.parentNode.insertBefore(prevBtn, rewindBtn);\n            }\n\n            \/\/ 创建下一集按钮(放在快进后)\n            if (fastForwardBtn && !document.querySelector('.plyr__control--episode-next')) {\n                const nextBtn = document.createElement('button');\n                nextBtn.className = 'plyr__control plyr__control--episode-next';\n                nextBtn.innerHTML = `\n                    <svg aria-hidden=\"true\" focusable=\"false\" width=\"18\" height=\"18\">\n                        <use xlink:href=\"#plyr-next-episode\"><\/use>\n                    <\/svg>\n                    <span class=\"plyr__tooltip\">下一集<\/span>\n                `;\n                nextBtn.onclick = (e) => {\n                    e.preventDefault();\n                    switchToNextEpisode(currentVideoUrl, autonext);\n                };\n                fastForwardBtn.parentNode.insertBefore(nextBtn, fastForwardBtn.nextSibling);\n            }\n        }\n    }\n}\n\n\/\/ 设置手势控制\nfunction gestureControls(player, video, speed) {\n    const gesture = {\n        startX: 0,\n        startY: 0,\n        startTime: 0,\n        isLongPress: false,\n        initialSpeed: 1,\n        isSeeking: false,\n        longPressTimer: null,\n        wasPlaying: false,\n        lastTime: 0,\n        initialBrightness: 1,\n        isBrightnessControl: false,\n        initialVolume: 1,\n        isVolumeControl: false,\n        gestureType: null,\n        threshold: 15\n    };\n\n    \/\/ 提示工具\n    const controlTooltip = document.createElement('div');\n    controlTooltip.className = 'plyr__center-tooltip';\n    controlTooltip.innerHTML = `\n        <span class=\"control-icon\" style=\"font-weight:bold\"><\/span>\n        <span class=\"control-value\"><\/span>\n    `;\n    document.querySelector('.plyr__video-wrapper').appendChild(controlTooltip);\n\n    \/\/ 获取系统亮度(如果支持)\n    const getSystemBrightness = () => {\n        return parseFloat(localStorage.getItem('systemBrightness')) || 1;\n    };\n\n    \/\/ 设置系统亮度(模拟实现)\n    const setSystemBrightness = (brightness) => {\n        localStorage.setItem('systemBrightness', brightness.toString());\n        \n        if ('screen' in window && 'brightness' in window.screen) {\n            try {\n                window.screen.brightness = brightness;\n            } catch (e) {\n                console.log('Screen Brightness API not supported');\n            }\n        }\n        \n        let brightnessOverlay = document.getElementById('brightness-overlay');\n        if (!brightnessOverlay) {\n            brightnessOverlay = document.createElement('div');\n            brightnessOverlay.id = 'brightness-overlay';\n            brightnessOverlay.style.cssText = `\n                position: fixed;\n                top: 0;\n                left: 0;\n                width: 100%;\n                height: 100%;\n                background: black;\n                pointer-events: none;\n                z-index: 2147483647;\n                opacity: 0;\n                transition: opacity 0.2s ease;\n            `;\n            document.body.appendChild(brightnessOverlay);\n            \n            document.addEventListener('fullscreenchange', updateBrightnessOverlay);\n            document.addEventListener('webkitfullscreenchange', updateBrightnessOverlay);\n        }\n        brightnessOverlay.style.opacity = (1 - brightness).toString();\n        \n        updateBrightnessOverlay();\n    };\n\n    \/\/ 更新亮度覆盖层位置\n    const updateBrightnessOverlay = () => {\n        const brightnessOverlay = document.getElementById('brightness-overlay');\n        if (!brightnessOverlay) return;\n        \n        const isFullscreen = document.fullscreenElement || \n                           document.webkitFullscreenElement ||\n                           document.mozFullScreenElement ||\n                           document.msFullscreenElement;\n        \n        if (isFullscreen) {\n            const fullscreenElement = document.fullscreenElement || \n                                    document.webkitFullscreenElement ||\n                                    document.mozFullScreenElement ||\n                                    document.msFullscreenElement;\n            \n            if (fullscreenElement) {\n                if (fullscreenElement !== document.body) {\n                    if (!fullscreenElement.contains(brightnessOverlay)) {\n                        fullscreenElement.appendChild(brightnessOverlay);\n                    }\n                    brightnessOverlay.style.position = 'absolute';\n                    brightnessOverlay.style.zIndex = '2147483647';\n                } else {\n                    if (document.body.contains(brightnessOverlay)) {\n                        document.body.appendChild(brightnessOverlay);\n                    }\n                    brightnessOverlay.style.position = 'fixed';\n                    brightnessOverlay.style.zIndex = '2147483647';\n                }\n            }\n        } else {\n            if (!document.body.contains(brightnessOverlay)) {\n                document.body.appendChild(brightnessOverlay);\n            }\n            brightnessOverlay.style.position = 'fixed';\n            brightnessOverlay.style.zIndex = '2147483647';\n        }\n    };\n\n    \/\/ 检查是否在全屏模式\n    const isFullscreen = () => {\n        return document.fullscreenElement || \n               document.webkitFullscreenElement ||\n               document.mozFullScreenElement ||\n               document.msFullscreenElement;\n    };\n\n    \/\/ 检查是否在控制区域(全屏时排除顶部10%)\n    const isInControlArea = (startX, startY) => {\n        \/\/ 只有在全屏模式下才排除顶部区域\n        if (isFullscreen()) {\n            const screenHeight = window.innerHeight;\n            \/\/ 全屏时顶部10%区域用于系统手势(如下拉通知栏)\n            if (startY < screenHeight * 0.1) {\n                return false;\n            }\n        }\n        \n        return true;\n    };\n\n    \/\/ 确定手势类型\n    const determineGestureType = (diffX, diffY, startX) => {\n        \/\/ 如果已经确定了手势类型,就保持原样\n        if (gesture.gestureType) {\n            return gesture.gestureType;\n        }\n        \n        const absDiffX = Math.abs(diffX);\n        const absDiffY = Math.abs(diffY);\n        \n        \/\/ 只有当移动距离超过阈值时才确定手势类型\n        if (absDiffX < gesture.threshold && absDiffY < gesture.threshold) {\n            return null;\n        }\n        \n        \/\/ 判断主要移动方向\n        if (absDiffX > absDiffY) {\n            \/\/ 主要横向移动 - 进度控制\n            return 'seek';\n        } else {\n            \/\/ 主要纵向移动 - 根据起始位置判断是亮度还是音量\n            const isLeftSide = startX < window.innerWidth \/ 2;\n            return isLeftSide ? 'brightness' : 'volume';\n        }\n    };\n\n    \/\/ 显示控制提示\n    const showControlTooltip = (icon, value = '') => {\n        controlTooltip.style.opacity = '1';\n        controlTooltip.querySelector('.control-icon').textContent = icon;\n        controlTooltip.querySelector('.control-value').textContent = value;\n    };\n\n    \/\/ 隐藏控制提示\n    const hideControlTooltip = () => {\n        controlTooltip.style.opacity = '0';\n    };\n\n    \/\/ 长按倍数播放\n    const onTouchStart = (e) => {\n        if (e.touches?.length > 1) return;\n\n        const clientX = e.clientX || e.touches[0].clientX;\n        const clientY = e.clientY || e.touches[0].clientY;\n        \n        \/\/ 检查是否在控制区域(全屏时排除顶部10%)\n        if (!isInControlArea(clientX, clientY)) return;\n\n        gesture.startX = clientX;\n        gesture.startY = clientY;\n        gesture.startTime = video.currentTime;\n        gesture.initialSpeed = player.speed;\n        gesture.isLongPress = false;\n        gesture.isSeeking = false;\n        gesture.wasPlaying = !video.paused;\n        gesture.lastTime = Date.now();\n        gesture.gestureType = null;\n        \n        gesture.initialBrightness = getSystemBrightness();\n        gesture.initialVolume = video.volume;\n        gesture.isBrightnessControl = false;\n        gesture.isVolumeControl = false;\n\n        gesture.longPressTimer = setTimeout(() => {\n            if (!gesture.isSeeking && !gesture.gestureType) {\n                gesture.isLongPress = true;\n                player.speed = speed;\n                \n                \/\/ 显示长按倍速提示\n                showControlTooltip('倍速', `${speed}×`);\n            }\n        }, 800);\n    };\n\n    \/\/ 滑动控制进度、亮度和音量\n    const onTouchMove = (e) => {\n        if (!gesture.startX || gesture.isLongPress) return;\n\n        const currentX = e.clientX || e.touches[0].clientX;\n        const currentY = e.clientY || e.touches[0].clientY;\n        const diffX = currentX - gesture.startX;\n        const diffY = currentY - gesture.startY;\n        \n        \/\/ 确定手势类型(如果尚未确定)\n        if (!gesture.gestureType) {\n            gesture.gestureType = determineGestureType(diffX, diffY, gesture.startX);\n            if (gesture.gestureType) {\n                clearTimeout(gesture.longPressTimer);\n            }\n        }\n        \n        \/\/ 根据确定的手势类型执行相应操作\n        switch (gesture.gestureType) {\n            case 'brightness':\n                handleBrightnessControl(diffY);\n                break;\n                \n            case 'volume':\n                handleVolumeControl(diffY);\n                break;\n                \n            case 'seek':\n                handleSeekControl(diffX);\n                break;\n                \n            default:\n                \/\/ 手势类型未确定,不执行任何操作\n                return;\n        }\n        \n        gesture.lastTime = Date.now();\n    };\n\n    \/\/ 处理亮度控制\n    const handleBrightnessControl = (diffY) => {\n        const brightnessChange = -diffY \/ window.innerHeight;\n        let newBrightness = Math.max(0.1, Math.min(1, gesture.initialBrightness + brightnessChange));\n        \n        setSystemBrightness(newBrightness);\n        \n        showControlTooltip('亮度', `${Math.round(newBrightness * 100)}%`);\n    };\n\n    \/\/ 处理音量控制\n    const handleVolumeControl = (diffY) => {\n        const volumeChange = -diffY \/ window.innerHeight;\n        let newVolume = Math.max(0, Math.min(1, gesture.initialVolume + volumeChange));\n        \n        video.volume = newVolume;\n        player.volume = newVolume;\n        \n        const iconText = newVolume === 0 ? '静音' : '音量';\n        showControlTooltip(iconText, `${Math.round(newVolume * 100)}%`);\n    };\n\n    \/\/ 处理进度控制\n    const handleSeekControl = (diffX) => {\n        if (!gesture.isSeeking) {\n            gesture.isSeeking = true;\n            gesture.wasPlaying = !video.paused;\n            if (gesture.wasPlaying) {\n                document.querySelector('.plyr__control--overlaid').classList.add('hidden');\n                video.pause();\n            }\n        }\n        \n        const seekAmount = Math.round((diffX \/ window.innerWidth) * 300);\n        const newTime = Math.max(0, Math.min(\n            gesture.startTime + seekAmount,\n            video.duration\n        ));\n        \n        if (Math.abs(video.currentTime - newTime) >= 1) {\n            $(\".video-container\")[0].style.background = '#000';\n            video.currentTime = newTime;\n        }\n        \n        if (newTime <= 0) {\n            showControlTooltip('已到开头');\n        } else if (newTime >= video.duration) {\n            showControlTooltip('已到结尾');\n        } else {\n            const directionText = seekAmount > 0 ? '前进' : '后退';\n            showControlTooltip(directionText, `${Math.abs(seekAmount)}秒`);\n        }\n    };\n\n    \/\/ 触摸结束\n    const onTouchEnd = () => {\n        clearTimeout(gesture.longPressTimer);\n        if (gesture.isLongPress) {\n            player.speed = gesture.initialSpeed;\n            \/\/ 立即隐藏提示\n            hideControlTooltip();\n        }\n        else if (gesture.wasPlaying && !gesture.isLongPress && gesture.isSeeking) {\n            video.play().catch(e => console.log(\"播放恢复失败:\", e));\n            setTimeout(() => {\n                document.querySelector('.plyr__control--overlaid').classList.remove('hidden');\n            }, 100);\n            \/\/ 延迟隐藏提示\n            setTimeout(hideControlTooltip, 500);\n        } else {\n            \/\/ 其他情况延迟隐藏提示\n            setTimeout(hideControlTooltip, 500);\n        }\n        \n        \/\/ 重置所有状态\n        gesture.startX = 0;\n        gesture.isSeeking = false;\n        gesture.isLongPress = false;\n        gesture.isBrightnessControl = false;\n        gesture.isVolumeControl = false;\n        gesture.gestureType = null;\n    };\n\n    \/\/ 事件监听\n    const playerContainer = document.querySelector('.plyr__video-wrapper');\n    playerContainer.addEventListener('touchstart', onTouchStart, { passive: true });\n    playerContainer.addEventListener('mousedown', onTouchStart);\n    playerContainer.addEventListener('touchmove', onTouchMove, { passive: false });\n    playerContainer.addEventListener('mousemove', onTouchMove);\n    playerContainer.addEventListener('touchend', onTouchEnd);\n    playerContainer.addEventListener('mouseup', onTouchEnd);\n    playerContainer.addEventListener('mouseleave', onTouchEnd);\n\n    setSystemBrightness(getSystemBrightness());\n\n    document.addEventListener('fullscreenchange', updateBrightnessOverlay);\n    document.addEventListener('webkitfullscreenchange', updateBrightnessOverlay);\n\n    \/\/ 注入CSS样式\n    const style = document.createElement('style');\n    style.textContent = `\n        .plyr__center-tooltip {\n            position: absolute;\n            top: calc(50% - 75px);\n            left: 50%;\n            transform: translate(-50%, -50%);\n            background-color: transparent;\n            color: white;\n            padding: 10px 20px;\n            border-radius: 5px;\n            z-index: 1000;\n            font-size: 18px;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            pointer-events: none;\n            opacity: 0;\n            transition: opacity 0.3s ease;\n            text-shadow: 0 0 5px rgba(0, 0, 0, 0.6), 0 0 10px rgba(0, 0, 0, 0.3);\n        }\n        .plyr__center-tooltip span { \n            font-weight: bold; \n            text-shadow: inherit; \n        }\n        .plyr__center-tooltip .control-value { \n            font-weight: normal; \n        }\n        \n        #brightness-overlay {\n            background: black;\n            pointer-events: none;\n            opacity: 0;\n            transition: opacity 0.2s ease;\n            z-index: 2147483647 !important;\n        }\n    `;\n    document.head.appendChild(style);\n}\n\n\/\/ 根据当前方向更新按钮可见状态\nfunction updateButtonVisibility(autonext) {\n    if (!document.fullscreenElement || !autonext) return;\n    \n    const isPortrait = window.innerHeight > window.innerWidth;\n    if (isPortrait) {\n        $('[data-plyr=\"rewind\"], [data-plyr=\"fast-forward\"]').forEach(btn => {\n            btn.style.display = 'none';\n        });\n    } else {\n        $('[data-plyr=\"rewind\"], [data-plyr=\"fast-forward\"]').forEach(btn => {\n            btn.style.display = 'flex';\n        });\n    }\n}\n\n\/\/ 处理全屏状态\nfunction updateFullscreen(autonext, video) {\n    if (!document.fullscreenElement) {\n        \/\/ 退出全屏时的处理\n        video.style.height = '56.25vw';\n        video.style.removeProperty('min-height');\n        $('.plyr__menu__container > div').forEach(menu => {\n            menu.style.maxHeight = '44vw';\n            menu.style.overflowY = 'auto';\n        });\n        if (autonext) {\n            $('.plyr__control--episode-prev, .plyr__control--episode-next').forEach(btn => {\n                btn.style.display = 'none';\n            });\n        }\n        $('[data-plyr=\"rewind\"], [data-plyr=\"fast-forward\"]').forEach(btn => {\n            btn.style.display = 'flex';\n        });\n        setTimeout(updatePadding, 100);\n    } else {\n        \/\/ 进入全屏时的处理\n        video.style.minHeight = '100%';\n        video.style.removeProperty('height');\n        $('.plyr__menu__container > div').forEach(menu => {\n            menu.style.maxHeight = '';\n            menu.style.overflowY = '';\n        });\n        if (autonext) {\n            $('.plyr__control--episode-prev, .plyr__control--episode-next').forEach(btn => {\n                btn.style.display = 'flex';\n            });\n            updateButtonVisibility(autonext); \/\/ 根据当前方向更新按钮\n        }\n        \n        \/\/ 强制重绘\n        setTimeout(() => {\n            const videoWrapper = document.querySelector('.plyr__video-wrapper');\n            if (videoWrapper) {\n                videoWrapper.style.display = 'flex';\n                videoWrapper.style.alignItems = 'center';\n                videoWrapper.style.justifyContent = 'center';\n                video.style.maxWidth = '100%';\n                video.style.maxHeight = '100%';\n                video.style.width = 'auto';\n                video.style.height = 'auto';\n            }\n        }, 100);\n        setTimeout(updatePadding, 100);\n    }\n}\n\n\/\/ 设置全屏和按钮显示处理\nfunction fullscreenControls(autonext, video) {\n    \/\/ 添加事件监听器\n    document.addEventListener('fullscreenchange', () => updateFullscreen(autonext, video));\n    window.addEventListener('resize', () => updateButtonVisibility(autonext));\n    window.addEventListener('orientationchange', () => {\n        setTimeout(() => updateButtonVisibility(autonext), 100);\n    });\n}\n\n\/\/ 锁定屏幕方向为横屏(实时跟随设备方向)\nfunction lockLandscapeOrientation() {\n    if (screen.orientation && screen.orientation.lock) {\n        \/\/ 移除之前的监听器\n        if (lockLandscapeOrientation.currentListener) {\n            window.removeEventListener('deviceorientation', lockLandscapeOrientation.currentListener);\n        }\n        \n        let lastDirection = null;\n        let hasSetInitial = false;\n        const DEAD_ZONE = 20; \/\/ 死区范围,避免轻微偏转就切换\n        \n        const handleOrientation = (event) => {\n            const gamma = event.gamma;\n            const beta = event.beta; \/\/ 前后倾斜角度\n            \n            \/\/ 在死区内不切换方向\n            if (Math.abs(gamma) < DEAD_ZONE) {\n                return;\n            }\n            \n            let newDirection;\n            \n            \/\/ 根据屏幕朝向调整方向判断\n            if (Math.abs(beta) > 90) {\n                \/\/ 屏幕朝下,需要反向判断\n                if (gamma > 0) {\n                    newDirection = 'landscape-primary'; \/\/ 屏幕朝下时,向右倾斜用右横屏\n                } else {\n                    newDirection = 'landscape-secondary'; \/\/ 屏幕朝下时,向左倾斜用左横屏\n                }\n            } else {\n                \/\/ 屏幕朝上,正常判断\n                if (gamma > 0) {\n                    newDirection = 'landscape-secondary'; \/\/ 设备向右,左横屏\n                } else {\n                    newDirection = 'landscape-primary'; \/\/ 设备向左,右横屏\n                }\n            }\n            \n            \/\/ 初始设置:立即根据第一个有效的gamma值设置方向\n            if (!hasSetInitial) {\n                hasSetInitial = true;\n                lastDirection = newDirection;\n                console.log('初始设置方向:', newDirection, 'gamma:', gamma, 'beta:', beta, '屏幕朝下:', Math.abs(beta) > 90);\n                screen.orientation.lock(newDirection).catch(error => {\n                    console.warn('初始方向锁定失败:', error);\n                });\n                return;\n            }\n            \n            \/\/ 后续切换:只有当方向确实改变时才锁定\n            if (newDirection !== lastDirection) {\n                lastDirection = newDirection;\n                console.log('切换横屏方向:', newDirection, 'gamma:', gamma, 'beta:', beta, '屏幕朝下:', Math.abs(beta) > 90);\n                screen.orientation.lock(newDirection).catch(error => {\n                    console.warn('横屏方向切换失败:', error);\n                });\n            }\n        };\n        \n        \/\/ 开始监听\n        window.addEventListener('deviceorientation', handleOrientation);\n        lockLandscapeOrientation.currentListener = handleOrientation;\n        \n        \/\/ 不设置默认方向,等待第一个设备方向事件\n        console.log('等待设备方向数据...');\n    }\n}\n\n\/\/ 锁定屏幕方向为竖屏\nfunction lockPortraitOrientation() {\n    if (screen.orientation && screen.orientation.lock) {\n        \/\/ 移除横屏的监听器(如果有)\n        if (lockLandscapeOrientation.currentListener) {\n            window.removeEventListener('deviceorientation', lockLandscapeOrientation.currentListener);\n            lockLandscapeOrientation.currentListener = null;\n        }\n        \n        console.log('锁定竖屏方向');\n        \/\/ 强制锁定竖屏,忽略当前设备方向\n        screen.orientation.lock('portrait').catch(error => {\n            console.warn('竖屏锁定失败:', error);\n        });\n    }\n}\n\n\/\/ 解锁屏幕方向\nfunction unlockOrientation() {\n    if (screen.orientation && screen.orientation.unlock) {\n        screen.orientation.unlock();\n    }\n    \n    \/\/ 移除设备方向监听\n    if (lockLandscapeOrientation.currentListener) {\n        window.removeEventListener('deviceorientation', lockLandscapeOrientation.currentListener);\n        lockLandscapeOrientation.currentListener = null;\n    }\n}\n\n\/\/ 检测视频方向\nfunction checkVideoOrientation(player) {\n    const video = player.media;\n    if (video.videoWidth && video.videoHeight) {\n        const aspectRatio = video.videoWidth \/ video.videoHeight;\n        return aspectRatio > 1; \/\/ 大于1为横屏,小于1为竖屏\n    }\n    return false;\n}\n\n\/\/ 初始化播放器方向控制\nfunction initPlayerOrientationControl(player) {\n    let isLandscapeVideo = false;\n    \n    \/\/ 视频元数据加载时检测视频方向\n    player.on('loadedmetadata', () => {\n        isLandscapeVideo = checkVideoOrientation(player);\n        console.log('视频方向:', isLandscapeVideo ? '横屏' : '竖屏');\n    });\n    \n    \/\/ 进入全屏时根据视频方向锁定屏幕\n    player.on('enterfullscreen', () => {\n        isLandscapeVideo = checkVideoOrientation(player);\n        console.log('进入全屏,视频方向:', isLandscapeVideo ? '横屏' : '竖屏');\n        \n        \/\/ 立即锁定方向,不等待\n        if (isLandscapeVideo) {\n            lockLandscapeOrientation();\n        } else {\n            lockPortraitOrientation();\n        }\n    });\n    \n    \/\/ 退出全屏时解锁方向\n    player.on('exitfullscreen', () => {\n        console.log('退出全屏,解锁方向');\n        unlockOrientation();\n    });\n}\n\n\/\/ 添加顶部集数显示(上移动画版)\nfunction addEpisodeBarToPlayer() {\n    \/\/ 添加顶部集数显示条\n    const episodeBar = document.createElement('div');\n    episodeBar.className = 'plyr__episode-bar';\n    episodeBar.style.transform = 'translateY(0)';\n    episodeBar.style.opacity = '1';\n    \n    \/\/ 创建标题元素\n    const titleElement = document.createElement('div');\n    titleElement.className = 'plyr__episode-title';\n    \n    \/\/ 初始化集数标题\n    const updateEpisodeTitle = () => {\n        const activeButton = document.querySelector('.jishu button.active');\n        if (activeButton) {\n            titleElement.textContent = activeButton.textContent.trim();\n        }\n    };\n    \n    \/\/ 初始设置标题\n    updateEpisodeTitle();\n    episodeBar.appendChild(titleElement);\n    document.querySelector('.plyr__video-wrapper').appendChild(episodeBar);\n\n    \/\/ 同步播放器控件状态(顶部条上移,底部条下移)\n    const syncWithControls = () => {\n        const controls = player.elements.controls;\n        const controlsStyle = window.getComputedStyle(controls);\n        \n        \/\/ 根据底部控制栏状态决定顶部条状态\n        if (controlsStyle.transform === 'none' || controlsStyle.opacity === '1') {\n            \/\/ 控制栏显示时:顶部条下滑进入\n            episodeBar.style.transform = 'translateY(0)';\n            episodeBar.style.opacity = '1';\n        } else {\n            \/\/ 控制栏隐藏时:顶部条上滑隐藏\n            episodeBar.style.transform = 'translateY(-100%)';\n            episodeBar.style.opacity = '0';\n        }\n    };\n\n    \/\/ 使用Plyr原生事件同步状态\n    player.on('controlsshown', () => {\n        episodeBar.style.transform = 'translateY(0)';\n        episodeBar.style.opacity = '1';\n    });\n    \n    player.on('controlshidden', () => {\n        episodeBar.style.transform = 'translateY(-100%)';\n        episodeBar.style.opacity = '0';\n    });\n\n    \/\/ 监听集数切换事件\n    const observeEpisodeChange = () => {\n        const activeButtonObserver = new MutationObserver(() => {\n            updateEpisodeTitle();\n            \/\/ 切换集数时短暂显示\n            \/\/episodeBar.style.transform = 'translateY(0)';\n            \/\/episodeBar.style.opacity = '1';\n            \/\/setTimeout(syncWithControls, 500);\n        });\n\n        const buttons = document.querySelectorAll('.jishu button');\n        buttons.forEach(button => {\n            activeButtonObserver.observe(button, { attributes: true });\n        });\n    };\n\n    \/\/ 全屏适配\n    player.on('enterfullscreen exitfullscreen', () => {\n        if (player.fullscreen.active) {\n            episodeBar.style.height = '60px';\n            episodeBar.querySelector('.plyr__episode-title').style.fontSize = '18px';\n        } else {\n            episodeBar.style.height = '40px';\n            episodeBar.querySelector('.plyr__episode-title').style.fontSize = '14px';\n        }\n    });\n\n    \/\/ 初始同步\n    setTimeout(() => {\n        \/\/ 绑定与Plyr相同的鼠标事件\n        const container = player.elements.container;\n        \n        container.addEventListener('mousemove', () => {\n            episodeBar.style.transform = 'translateY(0)';\n            episodeBar.style.opacity = '1';\n        });\n        \n        container.addEventListener('mouseleave', syncWithControls);\n        \n        syncWithControls();\n        observeEpisodeChange();\n    }, 50);\n    \n    \/\/ 添加样式\n    const style = document.createElement('style');\n    style.textContent = `\n        .plyr__episode-bar {\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            height: 40px;\n            background: linear-gradient(\n              to bottom, \n              rgba(0,0,0,0.8) 0%,\n              rgba(0,0,0,0.5) 30%,\n              rgba(0,0,0,0) 100%\n            );\n            color: white;\n            display: flex;\n            align-items: center;\n            padding-left: 15px;\n            z-index: 3;\n            transform: translateY(-100%);\n            opacity: 0;\n            transition: all 0.4s ease-in-out;\n            pointer-events: none;\n        }\n        .plyr__episode-title {\n            font-size: 14px;\n            text-shadow: 0 0 5px rgba(0,0,0,0.5);\n            white-space: nowrap;\n            overflow: hidden;\n            text-overflow: ellipsis;\n            width: 75%;\n            display: block;   \n        }\n        .plyr--fullscreen .plyr__episode-bar {\n            height: 60px;\n            padding-left: 20px;\n        }\n        .plyr--fullscreen .plyr__episode-title {\n            font-size: 18px;\n        }\n    `;\n    document.head.appendChild(style);\n}\n\n\/\/ 添加跳过菜单项\nfunction addSkipMenuItems(currentVideoUrl, timeManager) {\n    \/\/ 等待设置菜单加载完成\n    setTimeout(() => {\n        const settingsMenu = document.querySelector('.plyr__menu__container');\n        if (!settingsMenu) return;\n\n        \/\/ 创建跳过菜单项\n        const skipMenuItem = document.createElement('div');\n        skipMenuItem.className = 'plyr__skip-menu-item';\n        skipMenuItem.style.margin = '0 8px';\n        skipMenuItem.style.boxShadow = `\n            0 -1px 0 #ccc, \n            0 -2px 0 var(--plyr-menu-back-border-shadow-color, #fff)\n        `;\n        skipMenuItem.innerHTML = `\n            <span style=\"display: block; text-align: center; font-size: 15px; margin: 8px;\">标记片头\/片尾<\/span>\n        `;\n        skipMenuItem.setAttribute('role', 'menuitem');\n\n        \/\/ 创建子菜单容器\n        const submenuContainer = document.createElement('div');\n        submenuContainer.className = 'plyr__skip-submenu';\n        submenuContainer.setAttribute('hidden', '');\n        submenuContainer.setAttribute('role', 'menu');\n        \n        \/\/ 创建返回项\n        const backItem = document.createElement('div');\n        backItem.style.margin = '4px 0';\n        backItem.style.boxShadow = `\n            0 1px 0 var(--plyr-menu-back-border-shadow-color, #fff), \n            0 2px 0 #ccc\n        `;\n        backItem.innerHTML = `\n            <span style=\"display: block; text-align: center; height: 32px; font-size: 15px; margin: 4px 10px;\">返回上级菜单<\/span>\n        `;\n        backItem.setAttribute('role', 'menuitem');\n\n        \/\/ 实时获取标记时间\n        const getMarkTimes = () => ({\n            head: localStorage.getItem('head' + currentVideoUrl),\n            end: localStorage.getItem('end' + currentVideoUrl)\n        });\n\n        \/\/ 创建子菜单项\n        const submenuItems = `\n            <div style=\"display: flex; align-items: center; justify-content: space-between; padding: 4px 8px; margin: 2px 4px;\">\n                <div style=\"display: flex; align-items: center;\">\n                    <div style=\"font-size: 14px;\" data-action=\"mark-start\" role=\"menuitem\">\n                        ${getMarkTimes().head ? '已设片头' : '标记片头'}\n                    <\/div>\n                    <div style=\"font-size: 14px; color: #999; margin-left: 10px;\" class=\"head-time-display\">\n                        ${getMarkTimes().head ? formatTime(getMarkTimes().head) : '无'}\n                    <\/div>\n                <\/div>\n            <\/div>\n            \n            <div style=\"display: flex; align-items: center; justify-content: space-between; padding: 4px 8px; margin: 2px 4px;\">\n                <div style=\"display: flex; align-items: center;\">\n                    <div style=\"font-size: 14px;\" data-action=\"mark-end\" role=\"menuitem\">\n                        ${getMarkTimes().end ? '已设片尾' : '标记片尾'}\n                    <\/div>\n                    <div style=\"font-size: 14px; color: #999; margin-left: 10px;\" class=\"end-time-display\">\n                        ${getMarkTimes().end ? formatTime(getMarkTimes().end) : '无'}\n                    <\/div>\n                <\/div>\n            <\/div>\n            \n            <div style=\"display: flex; justify-content: center; padding: 4px 8px; margin: 2px 0 0 0;\">\n                <div style=\"font-size: 14px;\" data-action=\"clear-marks\" role=\"menuitem\">\n                    清除全部标记\n                <\/div>\n            <\/div>\n        `;\n\n        submenuContainer.appendChild(backItem);\n        submenuContainer.insertAdjacentHTML('beforeend', submenuItems);\n\n        \/\/ 添加到DOM - 插入到菜单最后面\n        settingsMenu.appendChild(skipMenuItem);\n        settingsMenu.appendChild(submenuContainer);\n\n        \/\/ 存储原始菜单项\n        const originalMenuItems = Array.from(settingsMenu.children).filter(\n            child => !child.classList.contains('plyr__skip-menu-item') && \n                    !child.classList.contains('plyr__skip-submenu')\n        );\n\n        \/\/ 更新时间显示函数\n        function updateTimeDisplay() {\n            const { head, end } = getMarkTimes();\n            const headDisplay = submenuContainer.querySelector('.head-time-display');\n            const endDisplay = submenuContainer.querySelector('.end-time-display');\n            const markStartBtn = submenuContainer.querySelector('[data-action=\"mark-start\"]');\n            const markEndBtn = submenuContainer.querySelector('[data-action=\"mark-end\"]');\n            \n            if (headDisplay) {\n                headDisplay.textContent = head ? formatTime(head) : '无';\n                headDisplay.style.color = head ? '#4CAF50' : '#999';\n                markStartBtn.textContent = head ? '已设片头' : '标记片头';\n            }\n            \n            if (endDisplay) {\n                endDisplay.textContent = end ? formatTime(end) : '无';\n                endDisplay.style.color = end ? '#FF5722' : '#999';\n                markEndBtn.textContent = end ? '已设片尾' : '标记片尾';\n            }\n        }\n\n        \/\/ 添加事件监听\n        skipMenuItem.addEventListener('click', (e) => {\n            e.stopPropagation();\n            updateTimeDisplay();\n            originalMenuItems.forEach(item => item.style.display = 'none');\n            skipMenuItem.style.display = 'none';\n            submenuContainer.removeAttribute('hidden');\n        });\n\n        \/\/ 返回按钮点击事件\n        backItem.addEventListener('click', () => {\n            submenuContainer.setAttribute('hidden', '');\n            originalMenuItems.forEach(item => item.style.display = '');\n            skipMenuItem.style.display = '';\n        });\n\n        \/\/ 标记片头点击事件\n        submenuContainer.querySelector('[data-action=\"mark-start\"]').addEventListener('click', () => {\n            let headtime = getMarkTimes().head;\n            timeManager.setHeadTime(player.currentTime);\n            showTemporaryMessage(`已${headtime ? '重设' : '标记'}片头: ${formatTime(timeManager.headtime)}`);\n        });\n\n        \/\/ 标记片尾点击事件\n        submenuContainer.querySelector('[data-action=\"mark-end\"]').addEventListener('click', () => {\n            let endtime = getMarkTimes().end;\n            timeManager.setEndTime(player.duration - player.currentTime);\n            showTemporaryMessage(`已${endtime ? '重设' : '标记'}片尾: ${formatTime(timeManager.endtime)}`);\n        });\n\n        \/\/ 清除标记点击事件\n        submenuContainer.querySelector('[data-action=\"clear-marks\"]').addEventListener('click', () => {\n            timeManager.clearMarks();\n            showTemporaryMessage('已清除全部标记');\n        });\n\n        \/\/ 添加时间更新监听\n        timeManager.addListener(({ type }) => {\n            updateTimeDisplay();\n            \n            \/\/ 如果是清除操作,确保UI同步更新\n            if (type === 'clear') {\n                const markStartBtn = submenuContainer.querySelector('[data-action=\"mark-start\"]');\n                const markEndBtn = submenuContainer.querySelector('[data-action=\"mark-end\"]');\n                if (markStartBtn) markStartBtn.textContent = '标记片头';\n                if (markEndBtn) markEndBtn.textContent = '标记片尾';\n            }\n        });\n\n        \/\/ 显示临时消息\n        function showTemporaryMessage(message) {\n            const messageEl = document.createElement('div');\n            messageEl.className = 'plyr__skip-message';\n            messageEl.textContent = message;\n            document.querySelector('.plyr').appendChild(messageEl);\n            \n            setTimeout(() => {\n                messageEl.classList.add('fade-out');\n                setTimeout(() => messageEl.remove(), 300);\n            }, 2000);\n        }\n\n        \/\/ 添加样式\n        if (!document.querySelector('#plyrSkipMessageStyle')) {\n            const style = document.createElement('style');\n            style.id = 'plyrSkipMessageStyle';\n            style.textContent = `\n                .plyr__skip-message {\n                    position: fixed;\n                    top: 30px;\n                    left: 15px;\n                    transform: none;\n                    background-color: rgba(0, 0, 0, 0.5);\n                    color: white;\n                    padding: 10px 20px;\n                    border-radius: 4px;\n                    z-index: 1000;\n                    text-align: left;\n                    font-size: 14px;\n                    transition: opacity 0.3s ease;\n                }\n                .plyr__skip-message.fade-out {\n                    opacity: 0;\n                }\n                .plyr__skip-menu-item {\n                    cursor: pointer;\n                }\n                .plyr__skip-menu-item:hover {\n                    background-color: rgba(255,255,255,0.1);\n                }\n            `;\n            document.head.appendChild(style);\n        }\n\n        \/\/ 初始显示菜单项\n        skipMenuItem.style.display = '';\n\n        \/\/ 监听显示菜单项\n        function observeMainMenuState() {\n            const observer = new MutationObserver((mutations) => {\n                mutations.forEach((mutation) => {\n                    const homeMenu = document.querySelector('[id^=\"plyr-settings-\"][id$=\"-home\"]');\n                    const subMenu = document.querySelector('.plyr__skip-submenu');\n                    if (homeMenu && subMenu) {\n                        const homeIsHidden = homeMenu.hasAttribute('hidden');\n                        const subIsHidden = subMenu.hasAttribute('hidden');\n                        skipMenuItem.style.display = homeIsHidden || !subIsHidden ? 'none' : '';\n                    }\n                });\n            });\n\n            \/\/ 监听整个 document(因为菜单可能动态加载)\n            observer.observe(document.body, {\n                childList: true,    \/\/ 监听子元素增删\n                subtree: true,      \/\/ 监听所有后代元素\n                attributes: true,   \/\/ 监听属性变化\n                attributeFilter: ['hidden', 'id'] \/\/ 只监听 hidden 和 id 变化\n            });\n\n            \/\/ 初始检查\n            const initialHomeMenu = document.querySelector('[id^=\"plyr-settings-\"][id$=\"-home\"]');\n            const initialSubMenu = document.querySelector('.plyr__skip-submenu');\n            if (initialHomeMenu && initialSubMenu) {\n                const homeIsHidden = initialHomeMenu.hasAttribute('hidden');\n                const subIsHidden = initialSubMenu.hasAttribute('hidden');\n                skipMenuItem.style.display = homeIsHidden || !subIsHidden ? 'none' : '';\n            }\n        }\n\n        \/\/ 启动监听\n        observeMainMenuState();\n    }, 500);\n}\n\n\/\/ 格式化时间显示\nfunction formatTime(seconds) {\n    seconds = parseFloat(seconds);\n    const date = new Date(0);\n    date.setSeconds(seconds);\n    const timeString = date.toISOString().substr(11, 8);\n    return timeString.startsWith('00:') ? timeString.substr(3) : timeString;\n}\n\n\/\/ HLS播放-检测网络状况并自适应调整\nfunction getOptimalHlsConfig() {\n    const connection = navigator.connection || {};\n    const downlink = connection.downlink || 5;\n    \n    if (downlink > 10) {\n        return adaptiveConfigManager.getHighSpeedConfig();\n    } else if (downlink > 5) {\n        return adaptiveConfigManager.getMediumSpeedConfig();\n    } else {\n        return adaptiveConfigManager.getLowSpeedConfig();\n    }\n}\n\n\/\/ 自适应配置管理器\nconst adaptiveConfigManager = {\n    performanceMetrics: {\n        loadTimes: [],\n        bufferingEvents: 0,\n        averageLoadTime: 0,\n        lastUpdateTime: 0\n    },\n    \n    \/\/ 配置模板\n    configTemplates: {\n        highSpeed: {\n            \/\/ 缓冲区设置\n            maxBufferSize: 800 * 1024 * 1024,  \/\/ 800MB\n            maxBufferLength: 600,              \/\/ 10分钟缓冲区\n            maxMaxBufferLength: 1200,          \/\/ 20分钟最大缓冲区\n            backBufferLength: 300,             \/\/ 5分钟后缓冲区\n            \n            \/\/ 预加载优化\n            preloadTime: 90,                   \/\/ 预加载90秒\n            initialLiveManifestSize: 5,\n            \n            \/\/ 网络优化\n            maxLoadingDelay: 1,\n            maxSeekHole: 0.5,\n            maxFragLookUpTolerance: 0.05,\n            highBufferWatchdogPeriod: 0.5,\n            nudgeOffset: 0.02,\n            nudgeMaxRetry: 15,\n            maxStarvationDelay: 1\n        },\n        mediumSpeed: {\n            \/\/ 缓冲区设置\n            maxBufferSize: 600 * 1024 * 1024,  \/\/ 600MB\n            maxBufferLength: 450,              \/\/ 7.5分钟缓冲区\n            maxMaxBufferLength: 900,           \/\/ 15分钟最大缓冲区\n            backBufferLength: 240,             \/\/ 4分钟后缓冲区\n            \n            \/\/ 预加载优化\n            preloadTime: 75,                   \/\/ 预加载75秒\n            initialLiveManifestSize: 4,\n            \n            \/\/ 网络优化\n            maxLoadingDelay: 1.5,\n            maxSeekHole: 1,\n            maxFragLookUpTolerance: 0.08,\n            highBufferWatchdogPeriod: 1,\n            nudgeOffset: 0.03,\n            nudgeMaxRetry: 12,\n            maxStarvationDelay: 2\n        },\n        lowSpeed: {\n            \/\/ 缓冲区设置\n            maxBufferSize: 500 * 1024 * 1024,  \/\/ 500MB\n            maxBufferLength: 360,              \/\/ 6分钟缓冲区\n            maxMaxBufferLength: 720,           \/\/ 12分钟最大缓冲区\n            backBufferLength: 180,             \/\/ 3分钟后缓冲区\n            \n            \/\/ 预加载优化\n            preloadTime: 60,                   \/\/ 预加载60秒\n            initialLiveManifestSize: 3,\n            \n            \/\/ 网络优化\n            maxLoadingDelay: 2,\n            maxSeekHole: 1.5,\n            maxFragLookUpTolerance: 0.1,\n            highBufferWatchdogPeriod: 1.5,\n            nudgeOffset: 0.05,\n            nudgeMaxRetry: 15,\n            maxStarvationDelay: 3\n        }\n    },\n    \n    \/\/ 根据性能指标调整配置\n    adaptConfigBasedOnPerformance() {\n        const metrics = this.performanceMetrics;\n        \n        \/\/ 计算平均加载时间\n        if (metrics.loadTimes.length > 0) {\n            metrics.averageLoadTime = metrics.loadTimes.reduce((a, b) => a + b) \/ metrics.loadTimes.length;\n        }\n\n        \/\/ 基于性能指标选择配置\n        if (metrics.averageLoadTime < 2000 && metrics.bufferingEvents < 2) {\n            console.log('加载状况良好,使用高速配置');\n            return this.getHighSpeedConfig();\n        } else if (metrics.averageLoadTime < 5000 && metrics.bufferingEvents < 5) {\n            console.log('加载状况中等,使用平衡配置');\n            return this.getMediumSpeedConfig();\n        } else {\n            console.log('加载状况较差,使用保守配置');\n            return this.getLowSpeedConfig();\n        }\n    },\n    \n    getHighSpeedConfig() {\n        return { ...this.configTemplates.highSpeed };\n    },\n    \n    getMediumSpeedConfig() {\n        return { ...this.configTemplates.mediumSpeed };\n    },\n    \n    getLowSpeedConfig() {\n        return { ...this.configTemplates.lowSpeed };\n    },\n    \n    \/\/ 应用配置到HLS实例\n    applyConfigToHls(hlsInstance, config) {\n        Object.keys(config).forEach(key => {\n            if (hlsInstance.hasOwnProperty(key)) {\n                hlsInstance[key] = config[key];\n            }\n        });\n    },\n    \n    \/\/ 动态调整配置\n    adaptHlsConfig(hlsInstance) {\n        if (this.performanceMetrics.loadTimes.length >= 3) {\n            const newConfig = this.adaptConfigBasedOnPerformance();\n            this.applyConfigToHls(hlsInstance, newConfig);\n            console.log('已根据加载性能动态调整HLS配置');\n        }\n    },\n    \n    \/\/ 记录片段加载时间\n    recordFragmentLoadTime(duration) {\n        this.performanceMetrics.loadTimes.push(duration);\n        \n        \/\/ 只保留最近10个记录\n        if (this.performanceMetrics.loadTimes.length > 10) {\n            this.performanceMetrics.loadTimes.shift();\n        }\n    },\n    \n    \/\/ 记录缓冲事件\n    recordBufferingEvent() {\n        this.performanceMetrics.bufferingEvents++;\n    },\n    \n    \/\/ 重置性能指标\n    resetMetrics() {\n        this.performanceMetrics.loadTimes = [];\n        this.performanceMetrics.bufferingEvents = 0;\n        this.performanceMetrics.averageLoadTime = 0;\n    }\n};\n\n\/\/ HLS播放\nfunction initializeHlsPlayer(source, headersOrReferer = null, video, currentTime, callback) {\n    if (currentHls) {\n        currentHls.stopLoad();\n        currentHls.detachMedia();\n        currentHls.destroy();\n    }\n\n    \/\/ 处理参数兼容性\n    let headers = null;\n    if (headersOrReferer) {\n        if (typeof headersOrReferer === 'string') {\n            headers = { 'Referer': headersOrReferer };\n        } else {\n            headers = headersOrReferer;\n        }\n    }\n\n    \/\/ 广告拦截配置\n    const adBlockConfig = {\n        enabled: true, \/\/ 设为false来禁用广告拦截\n        \/\/ 广告片段识别关键词\n        adKeywords: ['\/ad\/', 'ad_', '_ad', 'commercial', 'midroll', 'preroll', 'postroll'],\n        \/\/ 处理方式:'skip'=跳过,'empty'=返回空数据\n        strategy: 'skip'\n    };\n\n    \/\/ 获取初始配置\n    const initialConfig = getOptimalHlsConfig();\n    \n    currentHls = new Hls({\n        ...initialConfig,\n        \/\/ 通用优化设置\n        enableWorker: true,\n        enableSoftwareAES: true,\n        stretchShortVideoTrack: true,\n        forceKeyFrameOnDiscontinuity: true,\n        optimizeAudioOnly: false,\n        lowLatencyMode: false,\n        \n        \/\/ 使用 xhrSetup\n        xhrSetup: function(xhr, url) {\n            const startTime = performance.now();\n            const connection = navigator.connection || {};\n            const downlink = connection.downlink || 5;\n            const timeout = downlink > 10 ? 90000 : downlink > 5 ? 60000 : 30000;\n            \n            \/\/ 设置超时\n            xhr.timeout = timeout;\n            \n            \/\/ 设置响应类型\n            xhr.responseType = 'arraybuffer';\n            \n            \/\/ 检查是否为广告片段\n            if (adBlockConfig.enabled && isAdSegment(url)) {\n                return handleAdRequest(xhr, url);\n            }\n            \n            \/\/ 添加自定义 headers\n            if (headers) {\n                Object.keys(headers).forEach(key => {\n                    xhr.setRequestHeader(key, headers[key]);\n                });\n            }\n            \n            \/\/ 监听加载完成事件\n            const originalSend = xhr.send;\n            xhr.send = function(body) {\n                this.addEventListener('loadend', function() {\n                    if (this.status >= 200 && this.status < 300) {\n                        const loadTime = performance.now() - startTime;\n                        adaptiveConfigManager.recordFragmentLoadTime(loadTime);\n                        \n                        \/\/ 如果加载时间过长,动态调整配置\n                        if (loadTime > 5000) {\n                            adaptiveConfigManager.adaptHlsConfig(currentHls);\n                        }\n                    }\n                });\n                originalSend.call(this, body);\n            };\n            \n            \/\/ 监听超时事件\n            xhr.addEventListener('timeout', function() {\n                console.warn('XHR request timed out after', timeout, 'ms');\n            });\n            \n            \/\/ 监听错误事件\n            xhr.addEventListener('error', function() {\n                console.error('XHR request failed');\n            });\n        }\n    });\n\n    \/\/ 广告检测函数\n    function isAdSegment(url) {\n        if (!adBlockConfig.enabled) return false;\n        \n        const lowerUrl = url.toLowerCase();\n        \n        \/\/ 检查URL是否包含广告关键词\n        for (const keyword of adBlockConfig.adKeywords) {\n            if (lowerUrl.includes(keyword.toLowerCase())) {\n                return true;\n            }\n        }\n        \n        return false;\n    }\n\n    \/\/ 处理广告请求\n    function handleAdRequest(xhr, url) {\n        console.log('拦截广告片段:', url.substring(0, 100));\n        \n        switch (adBlockConfig.strategy) {\n            case 'skip':\n                \/\/ 返回空数据,让HLS跳过这个片段\n                return createMockResponse(xhr);\n                \n            case 'empty':\n                \/\/ 返回一个小型的合法视频片段(避免解码错误)\n                return createEmptySegmentResponse(xhr);\n                \n            default:\n                return createMockResponse(xhr);\n        }\n    }\n\n    \/\/ 创建模拟响应(跳过广告)\n    function createMockResponse(xhr) {\n        \/\/ 设置XHR状态为已完成\n        Object.defineProperties(xhr, {\n            'readyState': { value: 4, writable: false },\n            'status': { value: 200, writable: false },\n            'response': { value: new ArrayBuffer(0), writable: false },\n            'responseText': { value: '', writable: false }\n        });\n        \n        \/\/ 异步触发事件\n        setTimeout(() => {\n            \/\/ 触发readyStateChange\n            if (xhr.onreadystatechange) {\n                xhr.onreadystatechange.call(xhr);\n            }\n            \n            \/\/ 触发load事件\n            if (xhr.onload) {\n                xhr.onload.call(xhr);\n            }\n            \n            \/\/ 触发原生事件\n            const events = ['readystatechange', 'load', 'loadend'];\n            events.forEach(eventName => {\n                try {\n                    xhr.dispatchEvent(new Event(eventName));\n                } catch (e) {\n                    \/\/ 忽略事件分发错误\n                }\n            });\n        }, 0);\n        \n        \/\/ 阻止实际网络请求\n        xhr.send = function() {\n            \/\/ 什么都不做,已经模拟了响应\n        };\n        \n        return true;\n    }\n\n    \/\/ 创建空片段响应(更稳定的方案)\n    function createEmptySegmentResponse(xhr) {\n        \/\/ 创建一个非常小的视频片段(避免解码器错误)\n        \/\/ 这是一个H.264 NAL unit的简单示例(空片段)\n        const emptySegment = new Uint8Array([\n            0x00, 0x00, 0x00, 0x01, \/\/ NALU start code\n            0x09, 0xF0,             \/\/ NALU type: Access Unit Delimiter\n            0x00, 0x00, 0x00, 0x01, \/\/ NALU start code\n            0x06, 0x05              \/\/ NALU type: SEI\n        ]);\n        \n        Object.defineProperties(xhr, {\n            'readyState': { value: 4, writable: false },\n            'status': { value: 200, writable: false },\n            'response': { value: emptySegment.buffer, writable: false }\n        });\n        \n        setTimeout(() => {\n            if (xhr.onload) xhr.onload.call(xhr);\n            try {\n                xhr.dispatchEvent(new Event('load'));\n            } catch (e) {\n                \/\/ 忽略错误\n            }\n        }, 10);\n        \n        xhr.send = function() {\n            \/\/ 阻止实际发送\n        };\n        \n        return true;\n    }\n\n    \/\/ 监听HLS事件来优化广告检测\n    currentHls.on(Hls.Events.FRAG_LOADING, (event, data) => {\n        const frag = data.frag;\n        \/\/ 可以在片段加载时进行额外检测\n        if (frag.duration < 10 && frag.duration > 0) {\n            \/\/ 短片段可能是广告,但这里只是记录不处理\n            frag._isPotentialAd = true;\n        }\n    });\n\n    currentHls.loadSource(source);\n    currentHls.attachMedia(video);\n\n    \/\/ 监听缓冲事件\n    currentHls.on(Hls.Events.ERROR, (event, data) => {\n        if (data.details === Hls.ErrorDetails.BUFFER_STALLED_ERROR) {\n            adaptiveConfigManager.recordBufferingEvent();\n            \n            \/\/ 如果有多次缓冲事件,立即调整配置\n            if (adaptiveConfigManager.performanceMetrics.bufferingEvents >= 3) {\n                adaptiveConfigManager.adaptHlsConfig(currentHls);\n            }\n        }\n        \n        \/\/ 处理广告拦截可能引起的错误\n        if (data.details === Hls.ErrorDetails.FRAG_LOAD_ERROR || \n            data.details === Hls.ErrorDetails.FRAG_LOAD_TIMEOUT) {\n            if (data.frag && isAdSegment(data.frag.url)) {\n                console.log('广告片段加载错误已忽略');\n                data.recovered = true;\n            }\n        }\n    });\n\n    currentHls.on(Hls.Events.MANIFEST_PARSED, () => {\n        if (currentTime > 0) {\n            video.currentTime = parseFloat(currentTime);\n            $(\".video-container\")[0].style.background = '#000';\n        }\n        if (callback) callback();\n    });\n\n    \/\/ 定期检查性能并调整配置(每30秒)\n    const performanceCheckInterval = setInterval(() => {\n        if (adaptiveConfigManager.performanceMetrics.loadTimes.length >= 5) {\n            adaptiveConfigManager.adaptHlsConfig(currentHls);\n        }\n    }, 30000);\n\n    \/\/ 清理interval当HLS实例被销毁时\n    currentHls.on(Hls.Events.DESTROYING, () => {\n        clearInterval(performanceCheckInterval);\n    });\n}\n\n\/\/ 可选:提供一个函数来启用\/禁用广告拦截\nfunction setAdBlockEnabled(enabled) {\n    if (window.initializeHlsPlayer && window.initializeHlsPlayer.adBlockConfig) {\n        window.initializeHlsPlayer.adBlockConfig.enabled = enabled;\n    }\n}\n\n\/\/ 可选:添加广告关键词\nfunction addAdKeyword(keyword) {\n    if (window.initializeHlsPlayer && window.initializeHlsPlayer.adBlockConfig) {\n        if (!window.initializeHlsPlayer.adBlockConfig.adKeywords.includes(keyword)) {\n            window.initializeHlsPlayer.adBlockConfig.adKeywords.push(keyword);\n        }\n    }\n}\n\n\/\/ 普通播放\nfunction initializeRegularPlayer(source, headersOrReferer = null, video, currentTime, callback) {\n    video.src = '';\n    video.load();\n    video.preload = \"auto\";\n\n    \/\/ 处理参数兼容性\n    let headers = null;\n    if (headersOrReferer) {\n        if (typeof headersOrReferer === 'string') {\n            headers = { 'Referer': headersOrReferer };\n        } else {\n            headers = headersOrReferer;\n        }\n    }\n\n    if (headers) {\n        const xhr = new XMLHttpRequest();\n        xhr.open('HEAD', source, true);\n        \n        Object.keys(headers).forEach(key => {\n            xhr.setRequestHeader(key, headers[key]);\n        });\n        \n        xhr.withCredentials = true;\n        xhr.timeout = 10000;\n        \n        xhr.onload = function() {\n            const acceptRanges = xhr.getResponseHeader('Accept-Ranges');\n            const contentLength = xhr.getResponseHeader('Content-Length');\n            \n            console.log('服务器响应:', {\n                acceptRanges: acceptRanges,\n                contentLength: contentLength,\n                status: xhr.status\n            });\n            \n            if (acceptRanges === 'bytes' && contentLength) {\n                console.log('服务器支持range请求,使用MediaSource播放');\n                setVideoSourceWithHeaders();\n            } else {\n                console.log('服务器不支持range请求,直接加载');\n                directLoadVideo();\n            }\n        };\n        \n        xhr.onerror = function() {\n            console.error('HEAD请求失败');\n            directLoadVideo();\n        };\n        \n        xhr.ontimeout = function() {\n            console.warn('HEAD请求超时');\n            directLoadVideo();\n        };\n        \n        try {\n            xhr.send();\n        } catch (error) {\n            console.error('发送HEAD请求异常:', error);\n            directLoadVideo();\n        }\n    } else {\n        directLoadVideo();\n    }\n\n    function setVideoSourceWithHeaders() {\n        if ('MediaSource' in window && MediaSource.isTypeSupported('video\/mp4')) {\n            console.log('使用MediaSource API');\n            const mediaSource = new MediaSource();\n            video.src = URL.createObjectURL(mediaSource);\n            \n            mediaSource.addEventListener('sourceopen', function() {\n                const mimeCodec = 'video\/mp4; codecs=\"avc1.42E01E,mp4a.40.2\"';\n                const sourceBuffer = mediaSource.addSourceBuffer(mimeCodec);\n                \n                const xhr = new XMLHttpRequest();\n                xhr.open('GET', source, true);\n                xhr.responseType = 'arraybuffer';\n                \n                Object.keys(headers).forEach(key => {\n                    xhr.setRequestHeader(key, headers[key]);\n                });\n                \n                xhr.onload = function() {\n                    if (xhr.status >= 200 && xhr.status < 300) {\n                        console.log('视频数据加载完成,大小:', (xhr.response.byteLength \/ 1024 \/ 1024).toFixed(2), 'MB');\n                        sourceBuffer.appendBuffer(xhr.response);\n                        sourceBuffer.addEventListener('updateend', function() {\n                            if (!sourceBuffer.updating) {\n                                mediaSource.endOfStream();\n                                handleVideoReady();\n                            }\n                        });\n                    } else {\n                        console.error('视频数据请求失败,状态码:', xhr.status);\n                        directLoadVideo();\n                    }\n                };\n                \n                xhr.onerror = function() {\n                    console.error('视频数据请求网络错误');\n                    directLoadVideo();\n                };\n                \n                xhr.send();\n            });\n        } else {\n            console.log('浏览器不支持MediaSource,直接设置src');\n            video.src = source;\n            video.addEventListener('loadedmetadata', function() {\n                handleVideoReady();\n            }, { once: true });\n            video.load();\n        }\n    }\n\n    function directLoadVideo() {\n        video.src = source;\n        video.load();\n        handleVideoReady();\n    }\n\n    function handleVideoReady() {\n        const onLoaded = () => {\n            if (currentTime > 0) {\n                video.currentTime = parseFloat(currentTime);\n                $(\".video-container\")[0].style.background = '#000';\n            }\n            if (callback) callback();\n        };\n\n        if (video.readyState >= 1) {\n            setTimeout(onLoaded, 0);\n        } else {\n            video.addEventListener('loadedmetadata', onLoaded, { once: true });\n        }\n    }\n}\n\n\/\/ 上一集函数\nfunction switchToPrevEpisode(currentVideoUrl, autonext) {\n    if (isSwitching || !autonext) return;\n\n    isSwitching = true;\n    localStorage.removeItem(currentVideoUrl);\n    const currentButton = $('.jishu button.active')[0];\n    const prevButton = currentButton?.previousElementSibling;\n\n    if (prevButton && prevButton.innerText !== \"\" && !localStorage.getItem(currentVideoUrl)) {\n        prevButton.click();\n    } else {\n        weblog('已经是第一集了!');\n    }\n}\n\n\/\/ 下一集函数\nfunction switchToNextEpisode(currentVideoUrl, autonext) {\n    if (isSwitching || !autonext) return;\n\n    isSwitching = true;\n    localStorage.removeItem(currentVideoUrl);\n    const currentButton = $('.jishu button.active')[0];\n    const nextButton = currentButton?.nextElementSibling;\n\n    if (nextButton && nextButton.innerText !== \"\") {\n        nextButton.click();\n    } else {\n        weblog('没有下一集了!');\n    }\n}\n\n\/\/ 切换视频质量的函数\nasync function changeVideoQuality(quality, sources) {\n    const video = $('video')[0];\n    const selectedSource = sources.find(source => source.size === quality.toString());\n    const currentTime = video.currentTime;\n    const wasPlaying = !video.paused;\n\n    if (wasPlaying) {\n        $(\".video-container\")[0].style.background = '#000';\n    }\n    $(\"body>p\")[0].innerHTML = `\n      <div style=\"display: flex; align-items: center; justify-content: space-between; width: 100%;\">\n        <a href=\"legadovideo:\/\/${encodeURIComponent(selectedSource.src)}\">\n          ${selectedSource.src}\n        <\/a>\n        <button \n          onclick=\"navigator.clipboard.writeText('${selectedSource.src.replace(\/'\/g, \"\\\\'\")}')\n            .then(() => weblog('链接已复制到剪贴板'))\n            .catch(() => weblog('复制链接失败'))\"\n        >\n          复制\n        <\/button>\n      <\/div>\n    `;\n\n    if (selectedSource) {\n        if (Hls.isSupported() && \/m3u8|hls\/.test(selectedSource.src)) {\n            initializeHlsPlayer(selectedSource.src, selectedSource.headersOrReferer, video, currentTime, () => {\n                if (wasPlaying) {\n                    video.play();\n                }\n            });\n        } else {\n            initializeRegularPlayer(selectedSource.src, selectedSource.headersOrReferer, video, currentTime, () => {\n                if (wasPlaying) {\n                    video.play();\n                }\n            });\n        }\n    }\n}\n\n\/\/ 页面信息高度调整\nfunction updatePadding() {\n    var videoheight = $(\"video\")[0].offsetHeight;\n    var h3height = $(\"h3\")[0].offsetHeight;\n    var selectedButton = $('#selected-jiekou > button')[0];\n    $(\"img\")[0].style.top = `calc(16px + ${videoheight}px + ${h3height}px)`;\n    $(\".all-info\")[0].style.paddingTop = `calc(20px + ${videoheight}px + ${h3height}px)`;\n    if ($('#selected-jiekou')[0] && $('#jiekou-list')[0]) {\n        $('#selected-jiekou')[0].style.top = `calc(16px + ${videoheight}px + ${h3height}px)`;\n        $('#jiekou-list')[0].style.top = `calc(15.9px + ${videoheight}px + ${h3height}px + ${selectedButton.offsetHeight}px)`;\n        $('#jiekou-list')[0].style.width = `${selectedButton.offsetWidth - 1.9}px`;\n    }\n}\nwindow.addEventListener('resize', updatePadding);\n\n\/\/ SVG图标JS动态注入\ndocument.write(`\n<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"display:none\">\n  <symbol id=\"plyr-next-episode\" viewBox=\"0 0 18 18\">\n    <path d=\"M4 1v16l10-8z\" stroke-width=\"0.5\"\/>\n    <rect x=\"14\" y=\"1\" width=\"4\" height=\"16\" rx=\"1\" stroke-width=\"0.5\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-prev-episode\" viewBox=\"0 0 18 18\">\n    <path d=\"M14 1v16L4 9z\" stroke-width=\"0.5\"\/>\n    <rect x=\"0\" y=\"1\" width=\"4\" height=\"16\" rx=\"1\" stroke-width=\"0.5\"\/>\n  <\/symbol>\n<\/svg>\n`);\n\ndocument.write(`\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE svg PUBLIC \"-\/\/W3C\/\/DTD SVG 1.1\/\/EN\" \"http:\/\/www.w3.org\/Graphics\/SVG\/1.1\/DTD\/svg11.dtd\">\n<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" xmlns:xlink=\"http:\/\/www.w3.org\/1999\/xlink\" style=\"display: none;\">\n  <symbol id=\"plyr-airplay\" viewBox=\"0 0 18 18\">\n    <path d=\"M16 1H2a1 1 0 00-1 1v10a1 1 0 001 1h3v-2H3V3h12v8h-2v2h3a1 1 0 001-1V2a1 1 0 00-1-1z\"\/>\n    <path d=\"M4 17h10l-5-6z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-captions-off\" viewBox=\"0 0 18 18\">\n    <path d=\"M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z\" fill-rule=\"evenodd\" fill-opacity=\".5\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-captions-on\" viewBox=\"0 0 18 18\">\n    <path d=\"M1 1c-.6 0-1 .4-1 1v11c0 .6.4 1 1 1h4.6l2.7 2.7c.2.2.4.3.7.3.3 0 .5-.1.7-.3l2.7-2.7H17c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1H1zm4.52 10.15c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41C8.47 4.96 7.46 3.76 5.5 3.76c-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69zm7.57 0c1.99 0 3.01-1.32 3.28-2.41l-1.29-.39c-.19.66-.78 1.45-1.99 1.45-1.14 0-2.2-.83-2.2-2.34 0-1.61 1.12-2.37 2.18-2.37 1.23 0 1.78.75 1.95 1.43l1.3-.41c-.28-1.15-1.29-2.35-3.25-2.35-1.9 0-3.61 1.44-3.61 3.7 0 2.26 1.65 3.69 3.63 3.69z\" fill-rule=\"evenodd\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-download\" viewBox=\"0 0 18 18\">\n    <path d=\"M9 13c.3 0 .5-.1.7-.3L15.4 7 14 5.6l-4 4V1H8v8.6l-4-4L2.6 7l5.7 5.7c.2.2.4.3.7.3zm-7 2h14v2H2z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-enter-fullscreen\" viewBox=\"0 0 18 18\">\n    <path d=\"M10 3h3.6l-4 4L11 8.4l4-4V8h2V1h-7zM7 9.6l-4 4V10H1v7h7v-2H4.4l4-4z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-exit-fullscreen\" viewBox=\"0 0 18 18\">\n    <path d=\"M1 12h3.6l-4 4L2 17.4l4-4V17h2v-7H1zM16 .6l-4 4V1h-2v7h7V6h-3.6l4-4z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-fast-forward\" viewBox=\"0 0 18 18\">\n    <path d=\"M7.875 7.171L0 1v16l7.875-6.171V17L18 9 7.875 1z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-logo-vimeo\" viewBox=\"0 0 18 18\">\n    <path d=\"M17 5.3c-.1 1.6-1.2 3.7-3.3 6.4-2.2 2.8-4 4.2-5.5 4.2-.9 0-1.7-.9-2.4-2.6C5 10.9 4.4 6 3 6c-.1 0-.5.3-1.2.8l-.8-1c.8-.7 3.5-3.4 4.7-3.5 1.2-.1 2 .7 2.3 2.5.3 2 .8 6.1 1.8 6.1.9 0 2.5-3.4 2.6-4 .1-.9-.3-1.9-2.3-1.1.8-2.6 2.3-3.8 4.5-3.8 1.7.1 2.5 1.2 2.4 3.3z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-logo-youtube\" viewBox=\"0 0 18 18\">\n    <path d=\"M16.8 5.8c-.2-1.3-.8-2.2-2.2-2.4C12.4 3 9 3 9 3s-3.4 0-5.6.4C2 3.6 1.3 4.5 1.2 5.8 1 7.1 1 9 1 9s0 1.9.2 3.2c.2 1.3.8 2.2 2.2 2.4C5.6 15 9 15 9 15s3.4 0 5.6-.4c1.4-.3 2-1.1 2.2-2.4.2-1.3.2-3.2.2-3.2s0-1.9-.2-3.2zM7 12V6l5 3-5 3z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-muted\" viewBox=\"0 0 18 18\">\n    <path d=\"M12.4 12.5l2.1-2.1 2.1 2.1 1.4-1.4L15.9 9 18 6.9l-1.4-1.4-2.1 2.1-2.1-2.1L11 6.9 13.1 9 11 11.1zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-pause\" viewBox=\"0 0 18 18\">\n    <path d=\"M6 1H3c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zm6 0c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1h-3z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-pip\" viewBox=\"0 0 18 18\">\n    <path d=\"M13.293 3.293L7.022 9.564l1.414 1.414 6.271-6.271L17 7V1h-6z\"\/>\n    <path d=\"M13 15H3V5h5V3H2a1 1 0 00-1 1v12a1 1 0 001 1h12a1 1 0 001-1v-6h-2v5z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-play\" viewBox=\"0 0 18 18\">\n    <path d=\"M15.562 8.1L3.87.225c-.818-.562-1.87 0-1.87.9v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-restart\" viewBox=\"0 0 18 18\">\n    <path d=\"M9.7 1.2l.7 6.4 2.1-2.1c1.9 1.9 1.9 5.1 0 7-.9 1-2.2 1.5-3.5 1.5-1.3 0-2.6-.5-3.5-1.5-1.9-1.9-1.9-5.1 0-7 .6-.6 1.4-1.1 2.3-1.3l-.6-1.9C6 2.6 4.9 3.2 4 4.1 1.3 6.8 1.3 11.2 4 14c1.3 1.3 3.1 2 4.9 2 1.9 0 3.6-.7 4.9-2 2.7-2.7 2.7-7.1 0-9.9L16 1.9l-6.3-.7z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-rewind\" viewBox=\"0 0 18 18\">\n    <path d=\"M10.125 1L0 9l10.125 8v-6.171L18 17V1l-7.875 6.171z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-settings\" viewBox=\"0 0 18 18\">\n    <path d=\"M16.135 7.784a2 2 0 01-1.23-2.969c.322-.536.225-.998-.094-1.316l-.31-.31c-.318-.318-.78-.415-1.316-.094a2 2 0 01-2.969-1.23C10.065 1.258 9.669 1 9.219 1h-.438c-.45 0-.845.258-.997.865a2 2 0 01-2.969 1.23c-.536-.322-.999-.225-1.317.093l-.31.31c-.318.318-.415.781-.093 1.317a2 2 0 01-1.23 2.969C1.26 7.935 1 8.33 1 8.781v.438c0 .45.258.845.865.997a2 2 0 011.23 2.969c-.322.536-.225.998.094 1.316l.31.31c.319.319.782.415 1.316.094a2 2 0 012.969 1.23c.151.607.547.865.997.865h.438c.45 0 .845-.258.997-.865a2 2 0 012.969-1.23c.535.321.997.225 1.316-.094l.31-.31c.318-.318.415-.781.094-1.316a2 2 0 011.23-2.969c.607-.151.865-.547.865-.997v-.438c0-.451-.26-.846-.865-.997zM9 12a3 3 0 110-6 3 3 0 010 6z\"\/>\n  <\/symbol>\n  <symbol id=\"plyr-volume\" viewBox=\"0 0 18 18\">\n    <path d=\"M15.6 3.3c-.4-.4-1-.4-1.4 0-.4.4-.4 1 0 1.4C15.4 5.9 16 7.4 16 9c0 1.6-.6 3.1-1.8 4.3-.4.4-.4 1 0 1.4.2.2.5.3.7.3.3 0 .5-.1.7-.3C17.1 13.2 18 11.2 18 9s-.9-4.2-2.4-5.7z\"\/>\n    <path d=\"M11.282 5.282a.909.909 0 000 1.316c.735.735.995 1.458.995 2.402 0 .936-.425 1.917-.995 2.487a.909.909 0 000 1.316c.145.145.636.262 1.018.156a.725.725 0 00.298-.156C13.773 11.733 14.13 10.16 14.13 9c0-.17-.002-.34-.011-.51-.053-.992-.319-2.005-1.522-3.208a.909.909 0 00-1.316 0zm-7.496.726H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z\"\/>\n  <\/symbol>\n<\/svg>\n`);\n\n\/\/ localStorage数据\nconst allStorage = {};\nfor (let i = 0; i < localStorage.length; i++) {\n    const key = localStorage.key(i);\n    try {\n        allStorage[key] = JSON.parse(localStorage.getItem(key));\n    } catch (e) {\n        allStorage[key] = localStorage.getItem(key);\n    }\n}\n\/\/weblog(JSON.stringify(allStorage), 'localStorage 数据:', true);\n<\/script>\n<\/body>\n<\/html>\n<js>\nresult\n.replace(\/:\\s*\/g,':')\n.replace(\/\\{\\{.*播放源\\)\/g,'')\n.replace(\/<p>(?!.*集数)(.*:)<\\\/p>\/gm, '<p style=\"display:none;\">$1<\/p>');\n<\/js>",
    "ruleImage": "img.img-placeholder@src",
    "ruleLink": "a@href",
    "ruleNextPage": "page",
    "ruleTitle": "div.video-item-title@text",
    "singleUrl": false,
    "sortUrl": "搜索::\/search?text={{(source.getVariable()==''||source.getVariable()==null)?'丝袜':source.getVariable()}}&p={{page}}\n乱伦::\/main_ctg?id=8&p={{page}}\n強姦凌辱::\/main_ctg?id=2&p={{page}}\n内射受孕::\/main_ctg?id=12&p={{page}}\n巨乳美乳::\/main_ctg?id=9&p={{page}}\n出軌::\/main_ctg?id=7&p={{page}}\n角色劇情::\/main_ctg?id=6&p={{page}}\n絲襪美腿::\/main_ctg?id=1&p={{page}}\n制服誘惑::\/main_ctg?id=4&p={{page}}\n巨乳美乳::\/main_ctg?id=9&p={{page}}\n巨乳美乳::\/main_ctg?id=9&p={{page}}\n美腿::\/ctg?id=129&p={{page}}\n人妻::\/ctg?id=18&p={{page}}\n母親::\/ctg?id=88&p={{page}}\n痴女::\/ctg?id=65&p={{page}}\n女教师::\/ctg?id=69&p={{page}}",
    "sourceIcon": "https:\/\/hohoj.tv\/resources\/img\/logo.png",
    "sourceName": "HOHOJ",
    "sourceUrl": "https:\/\/hohoj.tv\/",
    "style": "* {\n    z-index: 0;\n    margin: 0;\n    padding: 0;\n}\n\nbody {\n    margin: auto;\n    background: #bbb;\n    width: 100%;\n}\n\nbody::before {\n    content: '';\n    position: fixed;\n    top: calc(56.25vw + 15px);\n    left: 0;\n    width: 100%;\n    height: calc(100% - (56.25vw + 15px));\n    background-image: var(--bg-image);\n    background-size: cover;\n    background-position: center;\n    opacity: var(--bg-opacity1);\n    z-index: -1;\n    pointer-events: none;\n}\n\nbody>p:first-of-type {\n    width: 100%;\n    position: fixed;\n    top: 0px;\n    text-indent: 0px;\n    height: 16px;\n    font-size: 0.7rem;\n    border-radius: 0px 0px 0px 0px;\n    background: #000;\n    z-index: 200;\n    display: flex;\n    align-items: center;\n}\n\nbody>p:first-of-type a {\n    color: #888;\n    text-decoration: none;\n    white-space: nowrap;\n    overflow-x: auto;\n    overflow-y: hidden;\n    display: block;\n    flex: 1;\n    min-width: 0;\n}\n\nbody>p:first-of-type button {\n    width: 30px;\n    height: 16px;\n    font-size: 0.6rem;\n    white-space: nowrap;\n    background: #333;\n    border: 0;\n    color: #888;\n    cursor: pointer;\n    flex-shrink: 0;\n    position: relative;\n    z-index: 201;\n}\n\nvideo {\n    visibility: hidden;\n}\n\n.video-container {\n    position: fixed;\n    top: 15px;\n    width: 100%;\n    height: 56.25vw;\n    z-index: 200;\n}\n\n#player {\n    position: relative;\n    width: 100%;\n}\n\n:root {\n    color-scheme: only light;\n    --plyr-color-main: #00aaff;\/* 播放器主要颜色 *\/\n    --plyr-control-color: #fff;\/* 播放器控件图标颜色 *\/\n    --plyr-control-background: transparent;\/* 播放器控件背景颜色 *\/\n    --plyr-video-background: transparent;\/* 视频背景颜色 *\/\n    --plyr-range-fill-background: #0099ee;\/* 进度条已填充部分的颜色 *\/\n    --plyr-range-thumb-background: #fff;\/* 进度条滑块的颜色 *\/\n}\n\n.plyr {\n    height: 100% !important;\n    width: 100% !important;\n    object-fit: cover;\n}\n\n.plyr__video-wrapper * {\n    touch-action: none !important;\n}\n\n.plyr__video-wrapper video {\n    touch-action: none !important;\n}\n\n.plyr__controls {\n    touch-action: none !important;\n}\n\n.plyr__control--overlaid {\n    background: transparent;\n    border: 0;\n    border-radius: 100%;\n    color: #fff;\n    left: calc(50% - 25px);\n    top: calc(50% - 45px);\n    transform: none;\n    width: 60px;\n    height: 48px;\n    padding: 0;\n    z-index: 2;\n}\n\n.plyr__control--overlaid svg {\n    width: 50px;\n    height: 50px;\n    left: calc(50% - 25px);\n    top: calc(50% - 10px);\n    transform: none;\n    fill: #fff;\/* 大播放器控件图标颜色 *\/\n    filter: \n        drop-shadow(0 0 20px rgba(0, 0, 0, 0.3))\n        drop-shadow(0 0 30px rgba(0, 0, 0, 0.1));\n}\n\n.plyr__control--overlaid.hidden {\n    display: none !important;\n    opacity: 0 !important;\n    pointer-events: none !important;\n}\n\n.plyr--video .plyr__control.plyr__tab-focus,.plyr--video .plyr__control:hover,.plyr--video .plyr__control[aria-expanded=true] {\n    background: transparent;\/* 播放器控件悬停\/点击背景颜色 *\/\n    color: #00aaff;\/* 播放器控件悬停\/点击图标颜色 *\/\n}\n\n.plyr__controls .plyr__controls__item {\n    margin-left: auto;\n    margin: calc(var(--plyr-control-spacing,10px)\/4);\n}\n\n.plyr__time--duration {\n    display: inline-block !important;\n}\n\n.plyr__time+.plyr__time:before {\n    margin-right: 8px !important;\n}\n\n@media (max-width: 640px) {\n    .plyr__captions {\n        margin-bottom:-8px\n    }\n\n    .plyr__progress__container {\n        margin-right: 5px\n    }\n\n    .plyr__time {\n        position: absolute;\n        bottom: 29px;\n    }\n\n    .plyr__time--current {\n        left: 106px\n    }\n\n    .plyr__time+.plyr__time:before {\n        content: \"\"!important\n    }\n\n    .plyr__time--duration {\n        right: 110px;\n    }\n\n    .plyr__volume {\n        width: auto;\n        max-width: 32px!important;\n        min-width: 32px!important\n    }\n\n    input[id^=plyr-volume-] {\n        display: none!important;\n    }\n\n    .plyr--airplay-supported [data-plyr=airplay],.plyr--captions-enabled [data-plyr=captions],.plyr--pip-supported [data-plyr=pip] {\n        display: none!important;\n    }\n}\n\ndetails {\n    width: 100%;\n    height: auto;\n    margin: auto;\n}\n\ndetails>img {\n    position: fixed;\n    width: 100%;\n    max-height: 90vw;\n    object-fit: contain;\n    background: #000;\n    border-bottom: 0.5px solid #333;\n    top: calc(56.25vw + 16px + 1.5em);\n    z-index: 50;\n}\n\ndetails[open]>summary {\n    background: #bbb;\/* 标题背景颜色 *\/\n}\n\nsummary {\n    position: fixed;\n    background: rgba(221, 221, 221, var(--bg-opacity2));\/* 标题背景颜色和透明度 *\/\n    backdrop-filter: blur(5px);\n    -webkit-backdrop-filter: blur(5px);\n    color: #111;\/* 标题文字颜色 *\/\n    box-shadow: 0 0.5px 3px #555;\/* 标题阴影颜色 *\/\n    list-style: none;\n    width: 100%;\n    padding-top: calc(56.25vw + 16px);\n    outline: none;\n    line-height: 1.5;\n    text-align: left;\n    word-wrap: break-word;\n    z-index: 100;\n}\n\nsummary>h3 {\n    width: 95%;\n    margin: auto;\n}\n\nsummary::-webkit-details-marker {\n    display: none;\n}\n\n.all-info {\n    position: relative;\n    color: #333;\/* 详情信息文字颜色 *\/\n    margin: auto;\n    width: 100%;\n    height: auto;\n    padding-top: calc(56.25vw + 24px + 1.5em);\n}\n\n.all-info>div {\n    width: 100%;\n    margin: auto;\n}\n\n.all-info>p {\n    text-indent: 0px;\n}\n\n.all-info>div>p {\n    width: 90%;\n    margin: 5px 5%;\n    outline: none;\n    text-align: left;\n    word-wrap: break-word;\n}\n\n.jiekou {\n    display: flex;\n}\n\n#selected-jiekou {\n    display: flex;\n    position: fixed;\n    justify-content: flex-end;\n    top: calc(56.25vw + 19.9px + 1.5em);\n    width: 150px;\n    right: 5px;\n}\n\n#selected-jiekou button {\n    width: 125px;\n    height: 28px;\n    padding: 2px;\n    background: rgba(204, 204, 204, var(--bg-opacity2));\/* 已选接口背景颜色 *\/\n    backdrop-filter: blur(5px);\n    -webkit-backdrop-filter: blur(5px);\n    overscroll-behavior-y: none;\n    -webkit-overflow-scrolling: auto;\n    color: #4a89a9;\n    border: 1px solid #666;\n    border-radius: 0 0 3px 3px;\n    opacity: 0.5;\n}\n\n#jiekou-list {\n    display: none;\n    position: fixed;\n    overflow-y: auto;\n    max-height: 200px;\n    top: 0;\n    right: 5px;\n    background: rgba(204, 204, 204, var(--bg-opacity2));\/* 接口列表背景颜色 *\/\n    backdrop-filter: blur(5px);\n    -webkit-backdrop-filter: blur(5px);\n    border: 1px solid #888;\n    border-top: none;\n    border-radius: 0 0 3px 3px;\n    z-index: 100;\n    margin: auto;\n}\n\n#jiekou-list button {\n    display: block;\n    width: 100%;\n    height: 28px;\n    margin: auto;\n    padding: 2px;\n    background: rgba(187, 187, 187, 0.1);\/* 待选接口按钮背景颜色 *\/\n    color: #333;\/* 待选接口按钮文字颜色 *\/\n    border: 1px solid #888;\n    border-top: none;\n    border-left: none;\n    border-right: none;\n    border-radius: 0px;\n    cursor: pointer;\n}\n\n#jiekou-list button:last-of-type {\n    border-bottom: none;\n    border-radius: 0 0 3px 3px;\n}\n\nsup {\n    font-size: 0.4em;\n    vertical-align: top;\n    position: relative;\n    top: -0.6em;\n}\n\n.jishu button:hover {\n    background: rgba(204, 204, 204, var(--bg-opacity2));\/* 按钮悬停背景颜色 *\/\n    border: 1px solid #4a89a9;\/* 按钮悬停边框颜色 *\/\n}\n\n.jishu button {\n    width: 30%;\n    margin: 1.25%;\n    padding: 5px;\n    outline: none;\n    border-radius: 8px;\n    border: 1px solid #666;\n    background: rgba(204, 204, 204, var(--bg-opacity2));\/* 集数按钮背景颜色 *\/\n    color: #333;\/* 集数按钮文字颜色 *\/\n    font-size: 0.7rem;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n}\n\n.jishu button.active {\n    background: rgba(204, 204, 204, var(--bg-opacity2));\/* 已选集数按钮背景颜色 *\/\n    color: #4a89a9;\n    border: 1px solid #4a89a9;\n    position: sticky;\n    left: 0;\n    right: 0;\n}\n\n.popup {\n    position: fixed;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%) scale(0.95);\n    background-color: #ccc;\n    padding: 20px;\n    border-radius: 8px;\n    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);\n    z-index: 2147483647;\n    width: 80%;\n    max-height: 80vh;\n    overflow: auto;\n    font-family: system-ui, -apple-system, sans-serif;\n    opacity: 0;\n    transition: all 0.3s ease;\n    pointer-events: auto;\n}\n\n.titleElement {\n    margin: 0 0 16px 0;\n    color: #333;\n    font-size: 18px;\n    font-weight: 600;\n}\n\n.contentElement {\n    white-space: pre-wrap;\n    word-wrap: break-word;\n    overflow-wrap: break-word;\n    max-height: calc(80vh - 150px);\n    overflow-y: auto;\n    padding: 16px;\n    background-color: #bbb;\n    color: #000;\n    border-radius: 6px;\n    margin-bottom: 20px;\n    font-size: 14px;\n    line-height: 1.6;\n    border: 1px solid #999;\n}"
}
广告