(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();
}
})();