(function () { function isHttpOrHttps(url) { return /^https?:\/\//i.test(url || ""); } // 与 1028-91ly lieyanDetail 一致: // - 绝对地址(http/https)直接播放 // - 相对路径走同域 /h5/m3u8/ 代理(含 ?ticket= 等查询参数) function resolvePlayUrl(videoUrl) { if (!videoUrl) return ""; var raw = String(videoUrl).trim(); if (!raw) return ""; if (isHttpOrHttps(raw)) return raw; var path = raw.replace(/^\/+/, ""); if (path.indexOf("h5/m3u8/") === 0) { return "/" + path; } return "/h5/m3u8/" + path; } function supportsH265Playback() { var video = document.createElement("video"); if (!video || typeof video.canPlayType !== "function") { return false; } var mimeTypes = [ 'video/mp4; codecs="hev1.1.6.L93.B0"', 'video/mp4; codecs="hvc1.1.6.L93.B0"', 'application/vnd.apple.mpegurl; codecs="hev1.1.6.L93.B0"', 'application/vnd.apple.mpegurl; codecs="hvc1.1.6.L93.B0"', 'application/x-mpegURL; codecs="hev1.1.6.L93.B0"', 'application/x-mpegURL; codecs="hvc1.1.6.L93.B0"', ]; for (var i = 0; i < mimeTypes.length; i += 1) { var result = video.canPlayType(mimeTypes[i]); if (result === "probably" || result === "maybe") { return true; } } if ( window.MediaSource && typeof window.MediaSource.isTypeSupported === "function" ) { return ( window.MediaSource.isTypeSupported( 'video/mp4; codecs="hev1.1.6.L93.B0"' ) || window.MediaSource.isTypeSupported( 'video/mp4; codecs="hvc1.1.6.L93.B0"' ) ); } return false; } function choosePlaybackSource(defaultUrl, h265Url, canPlayH265) { var normalizedDefaultUrl = resolvePlayUrl(defaultUrl); var normalizedH265Url = resolvePlayUrl(h265Url); var useH265 = !!(canPlayH265 && normalizedH265Url); return { defaultUrl: normalizedDefaultUrl, h265Url: normalizedH265Url, initialUrl: useH265 ? normalizedH265Url : normalizedDefaultUrl, usingH265: useH265, }; } var HLS_CONFIG = { maxBufferLength: 30, maxMaxBufferLength: 60, liveSyncDurationCount: 3, fragLoadingTimeOut: 20000, manifestLoadingTimeOut: 10000, }; function isAppleNativeHlsBrowser() { var nav = window.navigator || {}; var ua = nav.userAgent || ""; var isIOS = /iPhone|iPad|iPod/i.test(ua) || (/Macintosh/i.test(ua) && Number(nav.maxTouchPoints) > 1); var isSafari = /Safari/i.test(ua) && !/Chrome|Chromium|CriOS|FxiOS|EdgiOS|OPiOS|Android/i.test(ua); if (!isIOS && !isSafari) return false; var video = document.createElement("video"); return !!( video && typeof video.canPlayType === "function" && (video.canPlayType("application/vnd.apple.mpegurl") || video.canPlayType("application/x-mpegURL")) ); } function canUseHlsJs() { // Safari 已有稳定的原生 HLS。若同时让 Hls.js 通过 MSE 接管, // DPlayer 切源时先写入 video.src,WebKit 会启动原生加载器,随后 // Hls.js 又 attach MediaSource,两套加载器竞争会导致切集卡死。 if (isAppleNativeHlsBrowser()) return false; return ( typeof window.Hls === "function" && typeof window.Hls.isSupported === "function" && window.Hls.isSupported() ); } function attachHlsJs(video, player) { if (!canUseHlsJs()) return; if ( player.plugins && player.plugins.hls && typeof player.plugins.hls.destroy === "function" ) { player.plugins.hls.destroy(); } var hls = new window.Hls(HLS_CONFIG); player.plugins = player.plugins || {}; player.plugins.hls = hls; hls.loadSource(video.src); hls.attachMedia(video); } function buildVideoOption(url, pic) { var option = { url: url, type: "hls", }; if (canUseHlsJs()) { // Chrome 会把原生 HLS 能力报告为 "maybe",但实际无法播放部分 // AES-128 HLS;使用自定义类型可绕过 DPlayer 的原生能力误判。 option.type = "hlsjs"; option.customType = { hlsjs: attachHlsJs, }; } if (pic) option.pic = pic; return option; } // Safari 的原生 HLS 在同一个 video 元素上直接改 src 时,旧资源的 // MediaPlayer 状态有概率不会释放,后续 play() 会一直停在 loading。 // DPlayer.switchVideo() 没有 remove src + load 的清理步骤,因此切集前 // 先显式终止旧资源,切换后再让 WebKit 重新装载新的 manifest。 function switchPlayerVideo(dp, videoOption) { if (!dp || typeof dp.switchVideo !== "function") return; var video = getVideoElement(dp); var usesNativeHls = videoOption && videoOption.type === "hls"; if (isAppleNativeHlsBrowser() && usesNativeHls && video) { try { video.pause(); video.removeAttribute("src"); video.load(); } catch (e) {} } dp.switchVideo(videoOption); if (isAppleNativeHlsBrowser() && usesNativeHls && video) { try { video.load(); } catch (e2) {} } } function resumePlayerAfterSwitch(dp) { var video = getVideoElement(dp); if (!video) return; var settled = false; function showManualPlayButton() { var poster = document.querySelector("[data-play-poster]"); var playBtn = document.querySelector("[data-play-poster-play]"); if (poster) poster.classList.remove("play-player-cover--hidden"); if (playBtn) playBtn.style.display = ""; } function attemptPlay() { if (settled || video !== getVideoElement(dp)) return; var result; try { // 直接调用原生 play(),让 DPlayer 通过 video 的 play 事件同步 // UI;也避免 DPlayer 吞掉 iOS 的 Promise 拒绝原因。 result = video.play(); } catch (e) { showManualPlayButton(); return; } if (result && typeof result.then === "function") { result .then(function () { settled = true; hidePosterOverlay(); }) .catch(function (err) { // canplay 前的 AbortError 属于切源过程,事件到达后再试; // NotAllowedError 则保留可点击播放入口,避免页面看似卡死。 if (err && err.name === "NotAllowedError") { settled = true; showManualPlayButton(); } }); } else if (!video.paused) { settled = true; hidePosterOverlay(); } } video.addEventListener("loadedmetadata", attemptPlay, { once: true }); video.addEventListener("canplay", attemptPlay, { once: true }); attemptPlay(); } function bindPlayerPoster(dp, player) { var poster = player && player.querySelector("[data-play-poster]"); var playBtn = player && player.querySelector("[data-play-poster-play]"); if (!dp || typeof dp.on !== "function") { return; } function hideOverlay() { if (poster) { poster.classList.add("play-player-cover--hidden"); } if (playBtn) { playBtn.style.display = "none"; } } dp.on("play", hideOverlay); if (playBtn) { playBtn.addEventListener("click", function () { dp.play(); }); } } function getVideoElement(dp) { if (dp && dp.video && typeof dp.video.addEventListener === "function") { return dp.video; } return null; } function bindH265Fallback(dp, source) { if (!dp || !source || !source.usingH265 || !source.defaultUrl) { return; } var downgraded = false; var videoEl = getVideoElement(dp); function downgrade(reason) { if (downgraded) return; downgraded = true; try { dp.switchVideo( buildVideoOption( source.defaultUrl, dp.video && dp.video.poster ? dp.video.poster : "" ) ); } catch (e) { if (videoEl) { videoEl.src = source.defaultUrl; if (typeof videoEl.load === "function") { videoEl.load(); } } } } if (typeof dp.on === "function") { dp.on("error", function (event) { downgrade(event && event.type ? event.type : "dplayer-error"); }); } if (videoEl) { videoEl.addEventListener("error", function () { var mediaError = videoEl.error; var reason = mediaError && mediaError.code ? "media-error-" + mediaError.code : "media-error"; downgrade(reason); }); } if ( dp.plugins && dp.plugins.hls && window.Hls && window.Hls.Events && window.Hls.Events.ERROR ) { dp.plugins.hls.on(window.Hls.Events.ERROR, function (_, data) { if (data && data.fatal) { downgrade(data.details || data.type || "hls-fatal-error"); } }); } } var playDp = null; var PLAY_SPEED_RATES = [0.75, 1, 1.25, 1.5, 2, 3]; var PLAY_SPEED_DEFAULT = 1; var playSpeedIndex = PLAY_SPEED_RATES.indexOf(PLAY_SPEED_DEFAULT); if (playSpeedIndex < 0) playSpeedIndex = 1; function formatPlaySpeed(rate) { var n = Number(rate); if (!n || Number.isNaN(n)) n = 1; if (Number.isInteger(n)) return n.toFixed(1) + "x"; return String(n) + "x"; } function currentPlaySpeed() { return PLAY_SPEED_RATES[playSpeedIndex] || PLAY_SPEED_DEFAULT; } function applyPlaySpeed(dp, rate) { if (!dp) return; var value = Number(rate) || PLAY_SPEED_DEFAULT; try { if (typeof dp.speed === "function") { dp.speed(value); } } catch (e) {} try { if (dp.video) { dp.video.playbackRate = value; } } catch (e2) {} var speedLabel = document.querySelector("[data-play-speed-label]"); if (speedLabel) speedLabel.textContent = formatPlaySpeed(value); syncSpeedMenuActive(); } function syncSpeedMenuActive() { var menu = document.querySelector("[data-play-speed-menu]"); if (!menu) return; var current = currentPlaySpeed(); menu.querySelectorAll("[data-play-speed-option]").forEach(function (btn) { var rate = Number(btn.getAttribute("data-play-speed-option")); var active = rate === current; btn.classList.toggle("is-active", active); btn.setAttribute("aria-selected", active ? "true" : "false"); var check = btn.querySelector("[data-play-speed-check]"); if (check) check.hidden = !active; }); } function setSpeedSheetOpen(open) { var btn = document.querySelector("[data-play-speed]"); if (open) { document.body.classList.remove("play-ep-sheet-open"); document.body.classList.remove("play-comment-sheet-open"); document.body.classList.add("play-speed-sheet-open"); if (btn) btn.setAttribute("aria-expanded", "true"); syncSpeedMenuActive(); } else { document.body.classList.remove("play-speed-sheet-open"); if (btn) btn.setAttribute("aria-expanded", "false"); } } function bindSpeedMenu(dp) { var btn = document.querySelector("[data-play-speed]"); var menu = document.querySelector("[data-play-speed-menu]"); var sheet = document.querySelector("[data-play-speed-sheet]"); if (!btn || !menu) return; if (!menu.getAttribute("data-speed-menu-ready")) { menu.setAttribute("data-speed-menu-ready", "1"); var displayRates = PLAY_SPEED_RATES.slice().reverse(); menu.innerHTML = displayRates .map(function (rate) { var index = PLAY_SPEED_RATES.indexOf(rate); var label = formatPlaySpeed(rate); if (rate === PLAY_SPEED_DEFAULT) { label += "(默认)"; } return ( '" ); }) .join(""); } if (!btn.getAttribute("data-speed-bound")) { btn.setAttribute("data-speed-bound", "1"); btn.addEventListener("click", function (e) { e.stopPropagation(); var open = !document.body.classList.contains("play-speed-sheet-open"); setSpeedSheetOpen(open); }); menu.addEventListener("click", function (e) { var option = e.target && e.target.closest ? e.target.closest("[data-play-speed-option]") : null; if (!option || !menu.contains(option)) return; e.stopPropagation(); var index = Number(option.getAttribute("data-play-speed-index")); if (Number.isNaN(index) || index < 0 || index >= PLAY_SPEED_RATES.length) { return; } playSpeedIndex = index; applyPlaySpeed(playDp || dp, currentPlaySpeed()); setSpeedSheetOpen(false); }); if (sheet) { sheet.querySelectorAll("[data-play-speed-close]").forEach(function (closeBtn) { closeBtn.addEventListener("click", function () { setSpeedSheetOpen(false); }); }); } } applyPlaySpeed(dp, currentPlaySpeed()); } // 复用同一 meta 对象:bindVideoEvents 只绑一次,切集时就地更新字段即可生效。 var playTrackerMeta = { media_id: "", video_id: "", video_title: "", video_type_id: "playlet", video_type_name: "短剧", video_content_type: "playlet", recommend_trace_id: "", video_tag_key: "", video_tag_name: "", }; var playViewTracked = false; function syncPlayTrackerMeta() { var root = document.querySelector("[data-play-page]"); var ds = (root && root.dataset) || {}; var player = document.querySelector("[data-play-player]"); var pds = (player && player.dataset) || {}; var episode = ds.playEpisode || ""; playTrackerMeta.media_id = ds.playletId || ""; playTrackerMeta.video_id = ds.playletId || ""; playTrackerMeta.video_title = ds.playTitle || ""; playTrackerMeta.video_type_id = "playlet"; playTrackerMeta.video_type_name = ds.playTag || "短剧"; playTrackerMeta.video_content_type = "playlet"; playTrackerMeta.recommend_trace_id = ""; playTrackerMeta.video_tag_key = pds.chapterId || ""; playTrackerMeta.video_tag_name = episode ? "第" + episode + "集" : ""; return playTrackerMeta; } function bindPlayTracker(dp) { if (!dp || !window.tracker || typeof window.tracker.bindVideoEvents !== "function") { return; } syncPlayTrackerMeta(); window.tracker.bindVideoEvents(dp, playTrackerMeta); } function nextEpisodeElement() { var active = document.querySelector(".play-ep-grid .play-ep--active"); if (!active) return null; var next = active.nextElementSibling; while (next) { if (next.matches && next.matches(".play-ep")) return next; next = next.nextElementSibling; } return null; } function syncDesktopNextControl() { var button = document.querySelector("[data-player-next]"); if (!button) return; var disabled = !nextEpisodeElement(); button.disabled = disabled; button.setAttribute("aria-disabled", disabled ? "true" : "false"); } function formatDesktopPlayerTime(value) { var seconds = Number(value); if (!Number.isFinite(seconds) || seconds < 0) seconds = 0; seconds = Math.floor(seconds); var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds % 3600) / 60); var rest = seconds % 60; var minuteText = String(minutes).padStart(2, "0"); var secondText = String(rest).padStart(2, "0"); if (hours > 0) { return String(hours).padStart(2, "0") + ":" + minuteText + ":" + secondText; } return minuteText + ":" + secondText; } function setupDesktopPlayerControls(dp) { var player = document.querySelector(".play-player .dplayer"); if (!player) return; var left = player.querySelector(".dplayer-icons-left"); var right = player.querySelector(".dplayer-icons-right"); var playButton = left && left.querySelector(".dplayer-play-icon"); var volume = left && left.querySelector(".dplayer-volume"); var fullscreen = right && right.querySelector(".dplayer-full"); var time = left && left.querySelector(".dplayer-time"); if (!left || !right || !playButton) return; if (!left.querySelector("[data-player-next]")) { var nextButton = document.createElement("button"); nextButton.className = "dplayer-icon play-player-next"; nextButton.type = "button"; nextButton.setAttribute("data-player-next", ""); nextButton.setAttribute("aria-label", "播放下一集"); nextButton.innerHTML = ''; nextButton.addEventListener("click", function () { var next = nextEpisodeElement(); if (next) next.click(); }); playButton.insertAdjacentElement("afterend", nextButton); } if (volume && fullscreen) { right.insertBefore(volume, fullscreen); } var settingButton = right.querySelector(".dplayer-setting-icon"); if (settingButton && !settingButton.querySelector(".play-player-speed-label")) { var speedLabel = document.createElement("span"); speedLabel.className = "play-player-speed-label"; speedLabel.textContent = "倍速"; settingButton.appendChild(speedLabel); } if (time) { Array.prototype.slice.call(time.childNodes).forEach(function (node) { if (node.nodeType !== 3) return; if (node.nodeValue.indexOf("/") >= 0) node.nodeValue = "/"; else node.nodeValue = ""; }); } function syncTime() { if (!dp || !dp.video) return; var current = player.querySelector(".dplayer-ptime"); var duration = player.querySelector(".dplayer-dtime"); if (current) current.textContent = formatDesktopPlayerTime(dp.video.currentTime); if (duration) duration.textContent = formatDesktopPlayerTime(dp.video.duration); } syncDesktopNextControl(); syncTime(); if (dp && typeof dp.on === "function") { dp.on("timeupdate", syncTime); dp.on("loadedmetadata", syncTime); dp.on("durationchange", syncTime); } } function initDPlayer() { if (typeof DPlayer === "undefined") return null; var container = document.querySelector(".dplayer-video"); if (!container) return null; var source = choosePlaybackSource( container.dataset.url || "", container.dataset.h265Url || "", supportsH265Playback() ); if (!source.initialUrl) return null; var videoOption = buildVideoOption( source.initialUrl, container.dataset.cover || "" ); try { playDp = new DPlayer({ container: container, autoplay: false, preload: "metadata", screenshot: false, airplay: false, chromecast: false, hotkey: true, mutex: true, contextmenu: [], video: videoOption, }); var player = container.closest(".play-player"); bindPlayerPoster(playDp, player); bindH265Fallback(playDp, source); bindPlayTracker(playDp); setupDesktopPlayerControls(playDp); return playDp; } catch (e) { return null; } } function bindH5PlayerUI(dp) { bindSpeedMenu(dp); var moreBtn = document.querySelector("[data-play-intro-more]"); var introEl = document.querySelector("[data-play-intro]"); if (moreBtn && introEl) { function syncIntroMore() { var expanded = introEl.classList.contains("is-expanded"); if (expanded) { moreBtn.hidden = false; moreBtn.style.visibility = ""; moreBtn.textContent = "收起"; return; } // Reserve button width, then detect clamp overflow. moreBtn.hidden = false; moreBtn.style.visibility = "hidden"; moreBtn.textContent = "展开"; void introEl.offsetHeight; var overflowing = introEl.scrollHeight > introEl.clientHeight + 1; moreBtn.style.visibility = ""; moreBtn.hidden = !overflowing; } moreBtn.addEventListener("click", function () { var expanded = introEl.classList.toggle("is-expanded"); moreBtn.textContent = expanded ? "收起" : "展开"; if (!expanded) syncIntroMore(); }); syncIntroMore(); window.addEventListener("resize", syncIntroMore); } var clearBtn = document.querySelector("[data-play-clear]"); if (clearBtn) { var clearIcon = clearBtn.querySelector("[data-play-clear-icon]"); clearBtn.addEventListener("click", function () { var focusing = document.body.classList.toggle("play-h5-focus"); clearBtn.setAttribute("aria-label", focusing ? "取消清屏" : "清屏"); if (clearIcon) { clearIcon.src = focusing ? "/static/images/full_screen_close.svg" : "/static/images/full_screen.svg"; } }); } var nextBar = document.querySelector("[data-play-next]"); var nextCountdown = document.querySelector("[data-play-next-countdown]"); function findNextChapterId() { var active = document.querySelector(".play-ep--active") || document.querySelector(".play-ep-sheet-item--active"); if (!active) return 0; var next = active.nextElementSibling; while (next) { if ( next.matches && (next.matches(".play-ep") || next.matches(".play-ep-sheet-item")) ) { var id = Number(next.getAttribute("data-chapter-id") || 0); if (id > 0 && next.getAttribute("data-play-switch") != null) { return id; } // 付费/VIP 锁定集:返回 id 以便弹窗 if (id > 0) return id; } next = next.nextElementSibling; } return 0; } function setNextCountdownVisible(visible, seconds) { if (!nextBar) return; nextBar.hidden = !visible; document.body.classList.toggle("play-next-countdown-visible", !!visible); if (visible && nextCountdown) { nextCountdown.textContent = "倒计时" + seconds + "s"; } } function updateNextCountdown() { var chapterId = findNextChapterId(); var video = dp && dp.video; var duration = video ? Number(video.duration) : 0; var currentTime = video ? Number(video.currentTime) : 0; var remaining = duration - currentTime; var shouldShow = chapterId > 0 && Number.isFinite(duration) && duration > 0 && Number.isFinite(remaining) && remaining > 0 && remaining <= 3; setNextCountdownVisible( shouldShow, shouldShow ? Math.max(1, Math.ceil(remaining)) : 3 ); } if (dp && typeof dp.on === "function") { dp.on("timeupdate", updateNextCountdown); dp.on("ended", function () { markCurrentEpisodeWatched(); var chapterId = findNextChapterId(); setNextCountdownVisible(false, 3); if (chapterId) switchToChapter(chapterId, { autoplay: true }); }); dp.on("play", function () { applyPlaySpeed(dp, currentPlaySpeed()); markCurrentEpisodeWatched(); updateNextCountdown(); }); dp.on("seeked", updateNextCountdown); dp.on("loadedmetadata", function () { setNextCountdownVisible(false, 3); }); } } var playPage = document.querySelector(".play-page"); var playletId = Number( (playPage && playPage.getAttribute("data-playlet-id")) || 0 ); var playletSlug = (playPage && playPage.getAttribute("data-playlet-slug")) || ""; var playTitle = (playPage && playPage.getAttribute("data-play-title")) || ""; var playCover = (playPage && playPage.getAttribute("data-play-cover")) || ""; var playDesc = (playPage && playPage.getAttribute("data-play-desc")) || ""; var playTag = (playPage && playPage.getAttribute("data-play-tag")) || "短剧"; var playFeatured = (playPage && playPage.getAttribute("data-play-featured")) === "1"; var playIsNew = (playPage && playPage.getAttribute("data-play-new")) === "1"; var playTotalEpisode = Number( (playPage && playPage.getAttribute("data-play-total-episode")) || 0 ); var currentChapterId = Number( (document.querySelector("[data-play-player]") && document .querySelector("[data-play-player]") .getAttribute("data-chapter-id")) || 0 ); var currentEpisode = Number( (playPage && playPage.getAttribute("data-play-episode")) || 0 ); var switchingChapter = false; var lastWatchReportKey = ""; function reportChapterWatch() { if (!currentChapterId || !window.HttpRequest) return; if (window.CookieUtil && !window.CookieUtil.isLoggedIn()) return; var key = String(playletId || 0) + ":" + currentChapterId; if (key === lastWatchReportKey) return; lastWatchReportKey = key; var body = { chapterId: currentChapterId }; if (playletId) body.playletId = playletId; window.HttpRequest.post("/api/playlet/chapter/watch/report", body).catch( function () { /* 静默失败:未登录/网络错误不打断播放 */ } ); } function markCurrentEpisodeWatched() { reportChapterWatch(); } function padEpisode(n) { var num = Number(n) || 0; return num < 10 ? "0" + num : String(num); } function buildPlayHref(chapterId) { var key = playletSlug || (playletId ? String(playletId) : ""); if (!key) return "/play/"; return "/play/" + encodeURIComponent(key) + "/" + chapterId + "/"; } function resolvePayGate(chapter, el) { if (chapter) { if (chapter.canWatch || chapter.isUnlocked) return 0; if (chapter.needUnlock) return 1; if (chapter.needVip) return 2; var payType = Number(chapter.payType || 0); if (payType === 1 || payType === 2) return payType; return 0; } if (el) { if (el.getAttribute("data-can-watch") === "1") return 0; if (el.getAttribute("data-need-unlock") === "1") return 1; if (el.getAttribute("data-need-vip") === "1") return 2; if (el.getAttribute("data-coin-dialog-open") != null) return 1; if (el.getAttribute("data-vip-dialog-open") != null) return 2; return Number(el.getAttribute("data-pay-type") || 0); } return 0; } function resolveUnlockCost(chapter, el) { if (chapter) { if (Number(chapter.costCoins) > 0) return Number(chapter.costCoins); if (Number(chapter.singleCoins) > 0) return Number(chapter.singleCoins); } if (el) return Number(el.getAttribute("data-single-coins") || 0); return 0; } function openPayForChapter(el, chapter) { var payGate = resolvePayGate(chapter, el); if (payGate === 1) { if (typeof window.openCoinDialog === "function") { var coinTrigger = document.createElement("button"); coinTrigger.setAttribute("data-coin-dialog-open", ""); coinTrigger.setAttribute( "data-chapter-id", String((chapter && chapter.id) || (el && el.getAttribute("data-chapter-id")) || "") ); coinTrigger.setAttribute( "data-playlet-id", String( (chapter && chapter.playletId) || playletId || (el && el.getAttribute("data-playlet-id")) || "" ) ); coinTrigger.setAttribute( "data-single-coins", String(resolveUnlockCost(chapter, el)) ); coinTrigger.style.display = "none"; document.body.appendChild(coinTrigger); coinTrigger.click(); coinTrigger.remove(); } return true; } if (payGate === 2) { var vipBtn = document.querySelector("[data-vip-dialog-open]"); if (vipBtn) vipBtn.click(); else if (typeof window.openVipDialog === "function") { window.openVipDialog(); } return true; } return false; } function updateEpisodeActive(chapterId) { document.querySelectorAll(".play-ep").forEach(function (el) { var isActive = Number(el.getAttribute("data-chapter-id") || 0) === chapterId; el.classList.toggle("play-ep--active", isActive); var coin = el.querySelector(".play-ep-coin"); var lock = el.querySelector(".play-ep-lock"); if (coin) coin.style.display = isActive ? "none" : ""; if (lock) lock.style.display = isActive ? "none" : ""; }); document.querySelectorAll(".play-ep-sheet-item").forEach(function (el) { var isActive = Number(el.getAttribute("data-chapter-id") || 0) === chapterId; el.classList.toggle("play-ep-sheet-item--active", isActive); var coin = el.querySelector(".play-ep-sheet-coin"); var lock = el.querySelector(".play-ep-sheet-lock"); if (coin) coin.hidden = isActive; if (lock) lock.hidden = isActive; }); syncDesktopNextControl(); } function updateChapterMeta(chapter) { var ep = Number(chapter.currentEpisode) || 0; currentEpisode = ep; currentChapterId = Number(chapter.id) || currentChapterId; lastWatchReportKey = ""; if (playPage) { playPage.setAttribute("data-play-episode", String(ep)); } var h5Ep = document.querySelector("[data-play-h5-ep]"); if (h5Ep) h5Ep.textContent = ep > 0 ? "第" + ep + "集" : "播放"; var infoEp = document.querySelector("[data-play-info-ep]"); if (infoEp) infoEp.textContent = ep > 0 ? "第 " + ep + " 集" : "—"; var infoSub = document.querySelector("[data-play-info-sub]"); if (infoSub) { infoSub.textContent = playTitle ? playTitle + " - 第" + padEpisode(ep) + "集" : ep > 0 ? "第" + padEpisode(ep) + "集" : "—"; } document.title = playTitle ? playTitle + " - 第" + ep + "集" : "播放"; var player = document.querySelector("[data-play-player]"); if (player) { player.setAttribute("data-chapter-id", String(chapter.id || "")); player.setAttribute("data-playlet-id", String(playletId || chapter.playletId || "")); player.setAttribute("data-pay-type", String(chapter.payType || 0)); player.setAttribute( "data-single-coins", String(resolveUnlockCost(chapter, null)) ); player.setAttribute( "data-can-watch", chapter.canWatch || chapter.isUnlocked ? "1" : "0" ); player.setAttribute( "data-need-unlock", chapter.needUnlock ? "1" : "0" ); player.setAttribute("data-need-vip", chapter.needVip ? "1" : "0"); player.setAttribute( "data-is-unlocked", chapter.isUnlocked ? "1" : "0" ); } var commentCount = document.querySelector("[data-play-comment-count]"); if (commentCount) { commentCount.textContent = String(chapter.countComment || 0); } document.querySelectorAll(".detail-comments").forEach(function (section) { section.setAttribute( "data-comment-object-id", String(chapter.id || "") ); section.dispatchEvent(new CustomEvent("play:reload-comments")); }); } function hidePosterOverlay() { var poster = document.querySelector("[data-play-poster]"); var playBtn = document.querySelector("[data-play-poster-play]"); if (poster) poster.classList.add("play-player-cover--hidden"); if (playBtn) playBtn.style.display = "none"; } function ensurePlayerContainer(videoUrl) { var player = document.querySelector("[data-play-player]"); if (!player) return null; var container = player.querySelector(".dplayer-video"); if (container) { container.setAttribute("data-url", videoUrl || ""); if (playCover) container.setAttribute("data-cover", playCover); return container; } container = document.createElement("div"); container.className = "dplayer-video"; container.setAttribute("data-url", videoUrl || ""); if (playCover) container.setAttribute("data-cover", playCover); player.insertBefore(container, player.firstChild); var playBtn = player.querySelector("[data-play-poster-play]"); if (!playBtn) { playBtn = document.createElement("button"); playBtn.className = "play-player-center"; playBtn.type = "button"; playBtn.setAttribute("aria-label", "播放"); playBtn.setAttribute("data-play-poster-play", ""); playBtn.innerHTML = '播放'; player.appendChild(playBtn); } return container; } function switchToChapter(chapterId, options) { options = options || {}; chapterId = Number(chapterId) || 0; if (!chapterId || !window.HttpRequest) return Promise.resolve(); if (switchingChapter) return Promise.resolve(); if (chapterId === currentChapterId && playDp && !options.force) { if (options.autoplay !== false) { try { playDp.play(); } catch (e) {} } return Promise.resolve(); } switchingChapter = true; return window.HttpRequest.get("/api/playlet/chapter/detail", { params: { chapterId: chapterId, playletId: playletId || undefined, playletSlug: playletSlug || undefined, }, }) .then(function (res) { if (!res || res.code !== 200 || !res.data) { throw new Error((res && (res.tip || res.msg)) || "加载失败"); } var chapter = res.data; var payGate = resolvePayGate(chapter, null); if (payGate !== 0 && !options.skipPayCheck) { openPayForChapter(null, chapter); return; } var videoUrl = resolvePlayUrl(chapter.videoUrl || ""); if (!videoUrl) { if (payGate !== 0) { openPayForChapter(null, chapter); return; } throw new Error("暂无播放地址"); } currentChapterId = Number(chapter.id) || chapterId; updateEpisodeActive(currentChapterId); updateChapterMeta(chapter); markCurrentEpisodeWatched(); if (!options.skipHistory) { var href = buildPlayHref(currentChapterId); var state = { chapterId: currentChapterId }; if (options.replace) { history.replaceState(state, "", href); } else { history.pushState(state, "", href); } } var videoOption = buildVideoOption(videoUrl, playCover); if (playDp && typeof playDp.switchVideo === "function") { switchPlayerVideo(playDp, videoOption); syncPlayTrackerMeta(); bindPlayTracker(playDp); } else { ensurePlayerContainer(chapter.videoUrl || ""); playDp = initDPlayer(); bindH5PlayerUI(playDp); } if (options.autoplay !== false && playDp) { resumePlayerAfterSwitch(playDp); applyPlaySpeed(playDp, currentPlaySpeed()); } else { applyPlaySpeed(playDp, currentPlaySpeed()); } document.body.classList.remove("play-ep-sheet-open"); var sheetMask = document.querySelector(".play-h5-sheet-mask"); if (sheetMask) sheetMask.style.display = ""; var epSheet = document.querySelector("[data-play-ep-sheet]"); if (epSheet) epSheet.style.display = ""; }) .catch(function (err) { toast((err && err.message) || "切换剧集失败", "error"); }) .finally(function () { switchingChapter = false; }); } function markEpisodeUnlocked(chapterId) { chapterId = Number(chapterId) || 0; if (!chapterId) return; var href = buildPlayHref(chapterId); document .querySelectorAll( '.play-ep[data-chapter-id="' + chapterId + '"], .play-ep-sheet-item[data-chapter-id="' + chapterId + '"]' ) .forEach(function (el) { el.removeAttribute("data-coin-dialog-open"); el.removeAttribute("data-vip-dialog-open"); el.removeAttribute("data-single-coins"); el.setAttribute("data-play-switch", ""); el.setAttribute("href", href || "javascript:;"); var coin = el.querySelector( ".play-ep-coin, .play-ep-sheet-coin" ); if (coin) coin.remove(); var lock = el.querySelector( ".play-ep-lock, .play-ep-sheet-lock" ); if (lock) lock.remove(); }); var player = document.querySelector("[data-play-player]"); if ( player && Number(player.getAttribute("data-chapter-id") || 0) === chapterId ) { player.setAttribute("data-pay-type", "0"); player.setAttribute("data-can-watch", "1"); player.setAttribute("data-need-unlock", "0"); player.setAttribute("data-need-vip", "0"); player.setAttribute("data-is-unlocked", "1"); player.removeAttribute("data-single-coins"); } } function playAfterChapterUnlock(detail) { detail = detail || {}; var chapterId = Number(detail.chapterId) || 0; if (!chapterId) return; if (detail.playletId) { playletId = Number(detail.playletId) || playletId; if (playPage) { playPage.setAttribute("data-playlet-id", String(playletId)); } } markEpisodeUnlocked(chapterId); switchToChapter(chapterId, { force: true, skipPayCheck: true, autoplay: true, replace: true, }); } window.playAfterChapterUnlock = playAfterChapterUnlock; function bindEpisodeSwitch() { document.addEventListener("click", function (e) { var el = e.target && e.target.closest ? e.target.closest("[data-play-switch]") : null; if (!el) return; var chapterId = Number(el.getAttribute("data-chapter-id") || 0); if (!chapterId) return; e.preventDefault(); switchToChapter(chapterId, { autoplay: true }); }); window.addEventListener("popstate", function (e) { var match = String(window.location.pathname || "").match(/\/play\/[^/]+\/(\d+)\/?$/); var chapterId = (e.state && e.state.chapterId) || Number((match && match[1]) || 0); if (!chapterId) return; switchToChapter(chapterId, { autoplay: true, skipHistory: true, force: true, }); }); if (currentChapterId) { history.replaceState( { chapterId: currentChapterId }, "", window.location.href ); } } function maybeOpenPayDialog() { var player = document.querySelector("[data-play-player]"); if (!player) return; var hasVideo = !!player.querySelector(".dplayer-video[data-url]"); if (hasVideo) return; openPayForChapter(player, null); } function bindSidebarRanges() { var ranges = document.querySelectorAll("[data-play-ep-ranges] .play-ep-range"); var episodes = document.querySelectorAll("[data-play-ep-grid] .play-ep"); if (!ranges.length || !episodes.length) return; function applyRange(start, end) { episodes.forEach(function (el) { var n = Number(el.getAttribute("data-ep")); var inRange = !Number.isNaN(n) && n >= start && n <= end; el.classList.toggle("is-range-hidden", !inRange); }); } function activate(btn) { ranges.forEach(function (item) { item.classList.toggle("play-ep-range--active", item === btn); }); applyRange(Number(btn.dataset.rangeStart), Number(btn.dataset.rangeEnd)); } ranges.forEach(function (btn) { btn.addEventListener("click", function () { activate(btn); }); }); var active = document.querySelector(".play-ep-range--active") || ranges[0]; activate(active); } var openBtns = document.querySelectorAll("[data-play-ep-open]"); var closeBtns = document.querySelectorAll("[data-play-ep-close]"); var sheet = document.querySelector("[data-play-ep-sheet]"); var mask = document.querySelector(".play-h5-sheet-mask"); var commentOpenBtns = document.querySelectorAll("[data-play-comment-open]"); var commentCloseBtns = document.querySelectorAll("[data-play-comment-close]"); var commentSheet = document.querySelector("[data-play-comment-sheet]"); function openEpSheet() { document.body.classList.remove("play-comment-sheet-open"); document.body.classList.remove("play-speed-sheet-open"); document.body.classList.add("play-ep-sheet-open"); } function closeEpSheet() { document.body.classList.remove("play-ep-sheet-open"); } function openCommentSheet() { document.body.classList.remove("play-ep-sheet-open"); document.body.classList.remove("play-speed-sheet-open"); document.body.classList.add("play-comment-sheet-open"); var section = document.querySelector(".play-comment-sheet-body"); if (section) { section.dispatchEvent(new CustomEvent("play:reload-comments")); } } function closeCommentSheet() { document.body.classList.remove("play-comment-sheet-open"); } function closeSpeedSheet() { document.body.classList.remove("play-speed-sheet-open"); var btn = document.querySelector("[data-play-speed]"); if (btn) btn.setAttribute("aria-expanded", "false"); } function closeAllSheets() { closeEpSheet(); closeCommentSheet(); closeSpeedSheet(); } if (sheet) { openBtns.forEach(function (btn) { btn.addEventListener("click", openEpSheet); }); closeBtns.forEach(function (btn) { btn.addEventListener("click", closeEpSheet); }); var tabs = sheet.querySelectorAll(".play-ep-sheet-tab"); var epsPanel = sheet.querySelector('[data-ep-panel="eps"]'); var morePanel = sheet.querySelector('[data-ep-panel="more"]'); var epItems = sheet.querySelectorAll(".play-ep-sheet-item"); function applyRange(start, end) { epItems.forEach(function (el) { var n = Number(el.getAttribute("data-ep")); var inRange = !Number.isNaN(n) && n >= start && n <= end; el.classList.toggle("is-range-hidden", !inRange); }); } function activateTab(btn) { var tab = btn.getAttribute("data-ep-tab"); if (tab === "more") { tabs.forEach(function (item) { item.classList.toggle( "play-ep-sheet-tab--active", item.getAttribute("data-ep-tab") === "more" ); }); if (epsPanel) epsPanel.classList.add("is-hidden"); if (morePanel) morePanel.classList.remove("is-hidden"); return; } tabs.forEach(function (item) { item.classList.toggle("play-ep-sheet-tab--active", item === btn); }); if (epsPanel) epsPanel.classList.remove("is-hidden"); if (morePanel) morePanel.classList.add("is-hidden"); if (btn.dataset.rangeStart) { applyRange(Number(btn.dataset.rangeStart), Number(btn.dataset.rangeEnd)); return; } var rangeTab = sheet.querySelector('.play-ep-sheet-tab[data-ep-tab="eps"][data-range-start].play-ep-sheet-tab--active') || sheet.querySelector('.play-ep-sheet-tab[data-ep-tab="eps"][data-range-start]'); if (rangeTab) { applyRange(Number(rangeTab.dataset.rangeStart), Number(rangeTab.dataset.rangeEnd)); } } tabs.forEach(function (btn) { btn.addEventListener("click", function () { activateTab(btn); }); }); var activeTab = sheet.querySelector('.play-ep-sheet-tab[data-ep-tab="eps"][data-range-start].play-ep-sheet-tab--active') || sheet.querySelector(".play-ep-sheet-tab--active") || tabs[0]; if (activeTab) activateTab(activeTab); } if (commentSheet) { commentOpenBtns.forEach(function (btn) { btn.addEventListener("click", openCommentSheet); }); commentCloseBtns.forEach(function (btn) { btn.addEventListener("click", closeCommentSheet); }); } if (mask) { mask.addEventListener("click", closeAllSheets); } function toast(message, type) { if (typeof window.showToast === "function") { window.showToast(message, type); return; } window.alert(message); } function trackPlayView() { if (playViewTracked || !window.tracker || typeof window.tracker.videoEvent !== "function") return; var meta = syncPlayTrackerMeta(); if (!meta.video_id) return; playViewTracked = true; window.tracker.videoEvent({ media_id: meta.media_id, video_id: meta.video_id, video_title: meta.video_title, video_type_id: meta.video_type_id, video_type_name: meta.video_type_name, video_content_type: meta.video_content_type, recommend_trace_id: meta.recommend_trace_id, video_tag_key: meta.video_tag_key, video_tag_name: meta.video_tag_name, video_behavior_key: "video_view", video_behavior_name: "视频展示", }); } function bindTrackingWhenReady(dp) { function activateTracking() { bindPlayTracker(dp || playDp); trackPlayView(); } activateTracking(); if (!window.__trackerReady) { window.addEventListener("tracker:ready", activateTracking, { once: true }); } } function boot() { var dp = initDPlayer(); bindH5PlayerUI(dp); bindSidebarRanges(); bindEpisodeSwitch(); maybeOpenPayDialog(); bindTrackingWhenReady(dp); if (typeof window.initLazyImages === "function") { window.initLazyImages(document.querySelector(".play-page") || document); } } function bootWhenReady() { if (typeof DPlayer === "undefined") { window.addEventListener("load", bootWhenReady, { once: true }); return; } boot(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", bootWhenReady); } else { bootWhenReady(); } })();