Files
web_huangguo2_h5/app/static/js/pages/play.js
T

1282 lines
39 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function () {
if (typeof window.domClosest !== "function") {
window.domClosest = function (from, selector) {
var el = from;
if (!el) return null;
if (el.nodeType !== 1) el = el.parentElement || el.parentNode;
if (!el || el.nodeType !== 1 || typeof el.closest !== "function") return null;
return el.closest(selector);
};
}
// 参考 jha-1043:绝对地址直播,相对路径走同域 /h5/m3u8/ 代理。
function isHttpOrHttps(url) {
return /^https?:\/\//i.test(url || "");
}
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;
}
var HLS_CONFIG = {
maxBufferLength: 30,
maxMaxBufferLength: 60,
liveSyncDurationCount: 3,
fragLoadingTimeOut: 20000,
manifestLoadingTimeOut: 10000,
};
function forceCustomPlayer() {
var bc = window.BrowserCompat;
if (bc && bc.forceCustomPlayer) return true;
var ua = (window.navigator && window.navigator.userAgent) || "";
return /Quark|MQQBrowser|QQ\/|UCBrowser|UCWEB|MicroMessenger|baidubrowser|bdbrowser|BaiduHD|MiuiBrowser|HuaweiBrowser|HONORBrowser|HBPC|SogouMobileBrowser|VivoBrowser|HeyTapBrowser|OPPOBrowser|HeyTap|QihooBrowser|360Browser|360SE|360EE/i.test(
ua
);
}
function isIOSLike() {
var nav = window.navigator || {};
var ua = nav.userAgent || "";
return (
/iPhone|iPad|iPod/i.test(ua) ||
(/Macintosh/i.test(ua) && Number(nav.maxTouchPoints) > 1)
);
}
function canPlayNativeHls() {
var video = document.createElement("video");
return !!(
video &&
typeof video.canPlayType === "function" &&
(video.canPlayType("application/vnd.apple.mpegurl") ||
video.canPlayType("application/x-mpegURL"))
);
}
function isAppleNativeHlsBrowser() {
// 真 Safari / iOS Safari:原生 HLS 最稳。国产壳即便报告 mpegurl 也不走这条(易被系统播放器劫持)
if (forceCustomPlayer()) return false;
var ua = (window.navigator && window.navigator.userAgent) || "";
var isSafari =
/Safari/i.test(ua) &&
!/Chrome|Chromium|CriOS|FxiOS|EdgiOS|OPiOS|Android|Quark|MQQBrowser|UCBrowser|MicroMessenger|baidubrowser|MiuiBrowser|HuaweiBrowser|HONORBrowser|Sogou/i.test(
ua
);
if (!isIOSLike() && !isSafari) return false;
return canPlayNativeHls();
}
function canUseHlsJs() {
if (isAppleNativeHlsBrowser()) return false;
return (
typeof window.Hls === "function" &&
typeof window.Hls.isSupported === "function" &&
window.Hls.isSupported()
);
}
/** 非真 Safari 时等 hls.js 脚本就绪,避免 Android 国产壳误走原生 HLS */
function needsHlsScript() {
return !isAppleNativeHlsBrowser();
}
function attachHlsJs(video, player) {
var hlsOk =
typeof window.Hls === "function" &&
typeof window.Hls.isSupported === "function" &&
window.Hls.isSupported();
if (!hlsOk) return;
if (isAppleNativeHlsBrowser()) return;
if (
player.plugins &&
player.plugins.hls &&
typeof player.plugins.hls.destroy === "function"
) {
try {
player.plugins.hls.destroy();
} catch (err) {}
}
// DPlayer 会先把 m3u8 写进 video.src;夸克/X5 识别后会外跳系统播放器。
// 先清掉原生 src,仅通过 hls.js(MSE/blob) 喂流。
var src =
(video && (video.getAttribute("src") || video.src)) ||
(player &&
player.options &&
player.options.video &&
player.options.video.url) ||
"";
src = resolvePlayUrl(src);
try {
video.removeAttribute("src");
video.src = "";
if (typeof video.removeAttribute === "function") {
video.removeAttribute("src");
}
if (typeof video.load === "function") video.load();
} catch (e0) {}
applyInlineAttrs(video);
var hls = new window.Hls(HLS_CONFIG);
player.plugins = player.plugins || {};
player.plugins.hls = hls;
var recoverCount = 0;
hls.on(window.Hls.Events.ERROR, function (_evt, data) {
if (!data || !data.fatal) return;
if (recoverCount >= 3) return;
recoverCount += 1;
try {
if (data.type === window.Hls.ErrorTypes.NETWORK_ERROR) {
hls.startLoad();
} else if (data.type === window.Hls.ErrorTypes.MEDIA_ERROR) {
hls.recoverMediaError();
}
} catch (e) {}
});
if (src) hls.loadSource(src);
hls.attachMedia(video);
patchVideoInlinePlayback(player);
}
function buildVideoOption(url, pic) {
var option = { url: url, type: "hls" };
if (canUseHlsJs()) {
// Android / 桌面 / 支持 MSE 的壳:hls.js,避免系统播放器接管
option.type = "hlsjs";
option.customType = { hlsjs: attachHlsJs };
} else if (canPlayNativeHls()) {
// iOS 夸克/微信等无 MSE:只能原生 HLS,配合内联属性尽量页内播放
option.type = "hls";
} else {
option.type = "auto";
}
if (pic) option.pic = pic;
return option;
}
var videoPatchObserver = null;
var videoPatchedEl = null;
function stopVideoPatchWatch() {
if (videoPatchObserver) {
try {
videoPatchObserver.disconnect();
} catch (e) {}
videoPatchObserver = null;
}
videoPatchedEl = null;
}
/**
* 只盯 controls 被壳层写回的情况。
* 注意:不可在 Observer 里无条件 setAttribute(会再次触发回调,卡死页面)。
*/
function watchVideoNativeHijack(video) {
if (!video || !window.MutationObserver) return;
if (videoPatchedEl === video && videoPatchObserver) return;
stopVideoPatchWatch();
videoPatchedEl = video;
videoPatchObserver = new MutationObserver(function () {
if (video.controls || video.hasAttribute("controls")) {
try {
video.controls = false;
video.removeAttribute("controls");
} catch (err) {}
}
// 同层关键属性被清掉时再补一次(先 disconnect,避免自触发死循环)
if (video.getAttribute("x5-video-player-type") !== "h5") {
try {
videoPatchObserver.disconnect();
} catch (e1) {}
applyInlineAttrs(video);
try {
videoPatchObserver.observe(video, {
attributes: true,
attributeFilter: ["controls", "x5-video-player-type"],
});
} catch (e2) {}
}
});
try {
videoPatchObserver.observe(video, {
attributes: true,
attributeFilter: ["controls", "x5-video-player-type"],
});
} catch (e3) {}
}
function applyInlineAttrs(video) {
if (!video || !video.setAttribute) return;
// 同层播放关键属性:尽量在 video 创建前写好
video.setAttribute("playsinline", "true");
video.setAttribute("webkit-playsinline", "true");
video.setAttribute("x5-playsinline", "true");
video.setAttribute("t7-video-player-type", "inline");
video.setAttribute("x5-video-player-type", "h5");
// true = X5/夸克同层页内全屏;false 反而容易外跳系统播放器
video.setAttribute("x5-video-player-fullscreen", "true");
video.setAttribute("x5-video-orientation", "portrait");
video.setAttribute("controlslist", "nodownload noplaybackrate noremoteplayback");
video.setAttribute("disablepictureinpicture", "true");
video.setAttribute("webkit-airplay", "deny");
video.setAttribute("x-webkit-airplay", "deny");
video.removeAttribute("controls");
video.controls = false;
try {
video.playsInline = true;
} catch (e) {}
try {
video.disablePictureInPicture = true;
} catch (e2) {}
try {
if (video.disableRemotePlayback !== undefined) {
video.disableRemotePlayback = true;
}
} catch (e3) {}
}
/** 在 createElement('video') 时就打上同层属性,避免入 DOM 后再补已太晚 */
function installVideoCreateHook() {
if (document.__jhaVideoInlineHooked) return;
document.__jhaVideoInlineHooked = true;
var rawCreate = document.createElement.bind(document);
document.createElement = function (tagName, options) {
var el = rawCreate(tagName, options);
try {
if (String(tagName).toLowerCase() === "video") {
applyInlineAttrs(el);
}
} catch (err) {}
return el;
};
}
function exitNativeFullscreen(video) {
if (!video) return;
try {
if (typeof video.webkitExitFullscreen === "function") {
video.webkitExitFullscreen();
}
} catch (err) {}
try {
if (typeof video.exitFullscreen === "function") {
video.exitFullscreen();
}
} catch (err2) {}
}
function requestWebFullscreen(dp) {
if (!dp) return;
try {
if (dp.fullScreen && typeof dp.fullScreen.request === "function") {
dp.fullScreen.request("web");
return;
}
} catch (e) {}
try {
if (dp.container) {
dp.container.classList.add("dplayer-fulled");
document.body.classList.add("dplayer-web-fullscreen-fix");
}
} catch (e2) {}
}
/** 拦截 webkitEnterFullscreen,避免夸克/X5 调起系统播放器 */
function patchNativeFullscreenHooks(dp) {
var video = dp && dp.video;
if (!video || video.__fsHooked) return;
video.__fsHooked = true;
if (forceCustomPlayer()) {
var entering = false;
var blockNative = function () {
if (entering) return;
entering = true;
try {
exitNativeFullscreen(video);
requestWebFullscreen(dp);
applyInlineAttrs(video);
} finally {
entering = false;
}
};
try {
video.webkitEnterFullscreen = blockNative;
} catch (e) {}
try {
video.webkitEnterFullScreen = blockNative;
} catch (e2) {}
}
if (!video.__playHooked) {
video.__playHooked = true;
var nativePlay = video.play.bind(video);
video.play = function () {
applyInlineAttrs(video);
try {
return nativePlay();
} catch (err) {
return Promise.reject(err);
}
};
}
}
// QQ / 夸克 / UC / 百度等:页内同层播放,避免被系统播放器全屏接管
function patchVideoInlinePlayback(dp) {
var video = dp && dp.video;
if (!video) return;
applyInlineAttrs(video);
watchVideoNativeHijack(video);
patchNativeFullscreenHooks(dp);
if (!video.__cnPlayerBound) {
video.__cnPlayerBound = true;
var reapply = function () {
applyInlineAttrs(video);
};
video.addEventListener("play", reapply);
video.addEventListener("playing", reapply);
video.addEventListener("loadedmetadata", reapply);
video.addEventListener("x5videoenterfullscreen", reapply);
video.addEventListener("x5videoexitfullscreen", reapply);
video.addEventListener("webkitbeginfullscreen", function () {
if (forceCustomPlayer()) {
exitNativeFullscreen(video);
requestWebFullscreen(dp);
}
reapply();
});
}
}
/**
* 启用控制条原生播放/暂停,并补绑移动端被跳过的音量按钮。
* (插件在 isMobile 时不 initVolumeButton;我们已去掉 dplayer-mobile)
*/
function enableBarPlayControls(dp) {
if (!dp || !dp.container) return;
var el = dp.container;
function apply() {
el.classList.remove("dplayer-mobile");
if (el.offsetWidth <= 500) {
el.classList.add("dplayer-arrow");
}
}
apply();
if (typeof MutationObserver === "function" && !el.__barControlsObs) {
var obs = new MutationObserver(function () {
if (el.classList.contains("dplayer-mobile")) apply();
});
obs.observe(el, { attributes: true, attributeFilter: ["class"] });
el.__barControlsObs = obs;
}
if (!dp.__volumeInited) {
try {
if (dp.controller && typeof dp.controller.initVolumeButton === "function") {
dp.controller.initVolumeButton();
dp.__volumeInited = true;
} else {
bindVolumeMuteFallback(dp);
}
} catch (e) {
bindVolumeMuteFallback(dp);
}
}
}
function bindVolumeMuteFallback(dp) {
if (!dp || dp.__volumeInited) return;
var icon =
(dp.template && dp.template.volumeButtonIcon) ||
(dp.container && dp.container.querySelector(".dplayer-volume-icon"));
if (!icon || icon.__muteBound) return;
icon.__muteBound = true;
dp.__volumeInited = true;
icon.addEventListener("click", function (event) {
event.preventDefault();
event.stopPropagation();
try {
if (!dp.video) return;
dp.video.muted = !dp.video.muted;
if (typeof dp.switchVolumeIcon === "function") dp.switchVolumeIcon();
} catch (err) {}
});
}
/** 包装 DPlayer:任意实例都启用可操作的控制条按钮 */
function wrapDPlayerCtor() {
var Ctor = window.DPlayer;
if (!Ctor || Ctor.__barPlayWrapped) return;
installVideoCreateHook();
function Wrapped(options) {
installVideoCreateHook();
var dp = new Ctor(options);
enableBarPlayControls(dp);
patchVideoInlinePlayback(dp);
return dp;
}
Wrapped.prototype = Ctor.prototype;
Object.keys(Ctor).forEach(function (k) {
try {
Wrapped[k] = Ctor[k];
} catch (e) {}
});
Wrapped.__barPlayWrapped = true;
window.DPlayer = Wrapped;
}
function safePlay(dp) {
if (!dp) return;
try {
var result = dp.play();
if (result && typeof result.then === "function") {
result.catch(function () {
// 部分夸克/QQ 需再次用户手势;保留封面按钮可重试
});
}
} catch (e) {}
}
var playDp = null;
var currentPlayMode = "main";
var mainBadgePC = "";
var mainBadgeH5 = "";
var mainCtaLabel = "";
function playerRoot() {
return document.querySelector("[data-play-player]");
}
function isPreviewMode() {
var root = playerRoot();
return !!(
root &&
(root.getAttribute("data-play-mode") === "preview" ||
root.classList.contains("play-player--preview") ||
currentPlayMode === "preview")
);
}
function canWatchMain() {
var root = playerRoot();
return !!(root && root.getAttribute("data-can-watch") === "1");
}
function mainPlayURL() {
var root = playerRoot();
var container = document.querySelector(".dplayer-video");
var fromRoot = root && root.getAttribute("data-main-url");
var fromBox = container && container.getAttribute("data-main-url");
return resolvePlayUrl(fromRoot || fromBox || "");
}
function previewPlayURL() {
var root = playerRoot();
var container = document.querySelector(".dplayer-video");
var fromRoot = root && root.getAttribute("data-preview-url");
var fromBox = container && container.getAttribute("data-preview-url");
return resolvePlayUrl(fromRoot || fromBox || "");
}
function syncMainCta(mode) {
var cta = document.querySelector("[data-play-main-cta]");
if (!cta) return;
var label = cta.querySelector("[data-play-cta-label]");
if (!mainCtaLabel && label) mainCtaLabel = label.textContent || "";
if (mode === "preview") {
cta.removeAttribute("data-play-coin-open");
cta.setAttribute("data-play-poster-play", "");
if (label) label.textContent = "播放预告片";
return;
}
if (!canWatchMain()) {
cta.removeAttribute("data-play-poster-play");
cta.setAttribute("data-play-coin-open", "");
if (label) label.textContent = "解锁播放";
return;
}
cta.removeAttribute("data-play-coin-open");
cta.setAttribute("data-play-poster-play", "");
if (label) label.textContent = mainCtaLabel || "立即播放";
}
function hidePosterOverlay() {
var root = document.querySelector("[data-play-player]");
if (root) root.classList.add("is-playing");
var poster = document.querySelector("[data-play-poster]");
if (poster) poster.classList.add("play-player__cover--hidden");
var playBtn = document.querySelector("[data-play-poster-play]");
if (playBtn) playBtn.style.display = "none";
}
function showPosterOverlay() {
var root = document.querySelector("[data-play-player]");
if (root) root.classList.remove("is-playing");
var poster = document.querySelector("[data-play-poster]");
if (poster) poster.classList.remove("play-player__cover--hidden");
var playBtn = document.querySelector("[data-play-poster-play]");
if (playBtn) playBtn.style.display = "";
}
function ensurePosterDecrypt() {
var poster = document.querySelector(".play-player__cover.lazy[data-src]");
if (!poster) return;
if (typeof window.loadLazyNode === "function") {
window.loadLazyNode(poster);
return;
}
if (typeof window.initLazyImages === "function") {
window.initLazyImages(poster.parentNode || document, { eager: true });
}
}
function initDPlayer(initialURL) {
if (typeof window.DPlayer === "undefined") return null;
var container = document.querySelector(".dplayer-video");
if (!container) return null;
var url = resolvePlayUrl(initialURL || container.getAttribute("data-url") || "");
if (!url) return null;
try {
if (playDp && typeof playDp.destroy === "function") {
try {
playDp.destroy();
} catch (err) {}
playDp = null;
stopVideoPatchWatch();
container.innerHTML = "";
}
container.setAttribute("data-url", url);
installVideoCreateHook();
playDp = new window.DPlayer({
container: container,
autoplay: false,
preload: "metadata",
screenshot: false,
airplay: false,
chromecast: false,
hotkey: true,
mutex: true,
contextmenu: [],
video: buildVideoOption(url, ""),
});
// DPlayer 无 mobile 开关:去掉 dplayer-mobile,使用带播放/暂停的控制条
enableBarPlayControls(playDp);
patchVideoInlinePlayback(playDp);
if (typeof playDp.on === "function") {
playDp.on("loadedmetadata", function () {
patchVideoInlinePlayback(playDp);
});
playDp.on("play", function () {
patchVideoInlinePlayback(playDp);
hidePosterOverlay();
});
}
return playDp;
} catch (e) {
return null;
}
}
function switchPlayerSource(url) {
url = resolvePlayUrl(url || "");
if (!url) return null;
var container = document.querySelector(".dplayer-video");
if (container) container.setAttribute("data-url", url);
if (playDp && typeof playDp.switchVideo === "function") {
try {
playDp.switchVideo(buildVideoOption(url, ""));
patchVideoInlinePlayback(playDp);
return playDp;
} catch (e) {}
}
return initDPlayer(url);
}
function updatePlayBadge(mode) {
var pc = document.querySelector("[data-play-badge-pc]");
var h5 = document.querySelector("[data-play-badge-h5]");
if (!mainBadgePC && pc) mainBadgePC = pc.textContent || "";
if (!mainBadgeH5 && h5) mainBadgeH5 = h5.textContent || "";
if (mode === "preview") {
if (pc) pc.textContent = "预告片 免费";
if (h5) h5.textContent = "预告片";
return;
}
if (pc) pc.textContent = mainBadgePC;
if (h5) h5.textContent = mainBadgeH5;
}
function setPlayMode(mode, options) {
options = options || {};
var root = playerRoot();
if (!root) return;
var next = mode === "preview" ? "preview" : "main";
currentPlayMode = next;
root.setAttribute("data-play-mode", next);
root.classList.toggle("play-player--preview", next === "preview");
var tabs = document.querySelectorAll("[data-play-tab]");
Array.prototype.forEach.call(tabs, function (tab) {
tab.classList.toggle("is-active", tab.getAttribute("data-play-tab") === next);
});
var centerBtn = document.querySelector(".play-player__center");
updatePlayBadge(next);
syncMainCta(next);
if (next === "preview") {
if (centerBtn) centerBtn.removeAttribute("data-play-coin-open");
var previewURL = previewPlayURL();
if (!previewURL) return;
var dp = switchPlayerSource(previewURL);
bindPosterPlay(dp);
showPosterOverlay();
if (options.autoplay) {
patchVideoInlinePlayback(dp);
safePlay(dp);
hidePosterOverlay();
}
return;
}
// 正片
if (!canWatchMain()) {
if (centerBtn) centerBtn.setAttribute("data-play-coin-open", "");
if (playDp) {
try {
playDp.pause();
} catch (e) {}
}
showPosterOverlay();
return;
}
if (centerBtn) centerBtn.removeAttribute("data-play-coin-open");
var mainURL = mainPlayURL();
if (!mainURL) return;
var mainDp = switchPlayerSource(mainURL);
bindPosterPlay(mainDp);
showPosterOverlay();
if (options.autoplay) {
patchVideoInlinePlayback(mainDp);
safePlay(mainDp);
hidePosterOverlay();
}
}
function bindPlayTabs() {
var tabs = document.querySelectorAll("[data-play-tab]");
if (!tabs.length) return;
Array.prototype.forEach.call(tabs, function (tab) {
tab.addEventListener("click", function (event) {
event.preventDefault();
var mode = tab.getAttribute("data-play-tab") || "main";
if (mode === currentPlayMode) {
if (mode === "preview") {
setPlayMode("preview", { autoplay: true });
}
return;
}
setPlayMode(mode, { autoplay: mode === "preview" });
});
});
}
function bindPosterPlay(dp) {
var buttons = document.querySelectorAll("[data-play-poster-play]");
Array.prototype.forEach.call(buttons, function (btn) {
if (btn.getAttribute("data-play-bound") === "1") return;
btn.setAttribute("data-play-bound", "1");
btn.addEventListener("click", function (event) {
// 预告片直接播放;仅正片为 App 专享时弹下载提示
if (
btn.hasAttribute("data-play-coin-open") &&
!isPreviewMode()
) {
return;
}
if (btn.tagName === "A") {
event.preventDefault();
}
var mode = isPreviewMode() ? "preview" : "main";
var active = playDp || dp;
if (mode === "preview") {
if (!active) active = switchPlayerSource(previewPlayURL());
} else if (!canWatchMain()) {
return;
} else if (!active) {
active = switchPlayerSource(mainPlayURL());
}
if (!active) return;
patchVideoInlinePlayback(active);
safePlay(active);
hidePosterOverlay();
});
});
}
function tryAutoplayPreview() {
if (!isPreviewMode() || !previewPlayURL()) return;
setPlayMode("preview", { autoplay: true });
}
function bootPlayer() {
var root = playerRoot();
if (!root) return;
// 等 DPlayer(及非 Safari 场景下的 hls.js)就绪,避免误走原生 HLS / 未包装构造器
var libsPending =
typeof window.DPlayer === "undefined" ||
(needsHlsScript() && typeof window.Hls === "undefined");
if (libsPending) {
if (!bootPlayer._retries) bootPlayer._retries = 0;
if (bootPlayer._retries < 40) {
bootPlayer._retries += 1;
window.setTimeout(bootPlayer, 50);
}
return;
}
wrapDPlayerCtor();
ensurePosterDecrypt();
bindPlayTabs();
var pc = document.querySelector("[data-play-badge-pc]");
var h5 = document.querySelector("[data-play-badge-h5]");
if (pc) mainBadgePC = pc.textContent || "";
if (h5) mainBadgeH5 = h5.textContent || "";
var ctaLabel = document.querySelector("[data-play-cta-label]");
if (ctaLabel) mainCtaLabel = ctaLabel.textContent || "";
currentPlayMode = root.getAttribute("data-play-mode") || "main";
// 预告片:无需登录 / 解锁,可直接初始化并播放
if (currentPlayMode === "preview" && previewPlayURL()) {
var previewDp = initDPlayer(previewPlayURL());
bindPosterPlay(previewDp);
syncMainCta("preview");
showPosterOverlay();
document.addEventListener("eighteen:closed", tryAutoplayPreview, {
once: true,
});
if (
document.documentElement.classList.contains("eighteen-ok") ||
!document.querySelector("[data-eighteen].is-open")
) {
// 已过年龄门时不强制自动播,等用户点击即可
}
return;
}
if (canWatchMain() && mainPlayURL()) {
var dp = initDPlayer(mainPlayURL());
bindPosterPlay(dp);
if (!dp) showPosterOverlay();
return;
}
// 正片未解锁但有预告:切到预告片模式
if (previewPlayURL()) {
setPlayMode("preview", { autoplay: false });
document.addEventListener("eighteen:closed", tryAutoplayPreview, {
once: true,
});
return;
}
// 正片未解锁:仅绑定 tab / 封面
bindPosterPlay(null);
showPosterOverlay();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", bootPlayer);
} else {
bootPlayer();
}
})();
(function () {
var root = document.querySelector("[data-play-coin]");
if (!root) return;
var openers = document.querySelectorAll("[data-play-coin-open]");
var closers = root.querySelectorAll("[data-play-coin-close]");
var previousOverflow = "";
function open() {
root.classList.add("is-open");
previousOverflow = document.body.style.overflow;
document.body.style.overflow = "hidden";
}
function close() {
root.classList.remove("is-open");
document.body.style.overflow = previousOverflow;
}
function isPreviewPlayingSurface() {
var player = document.querySelector("[data-play-player]");
return !!(
player &&
(player.getAttribute("data-play-mode") === "preview" ||
player.classList.contains("play-player--preview"))
);
}
Array.prototype.forEach.call(openers, function (btn) {
btn.addEventListener("click", function () {
// 预告片模式不弹下载提示;属性被临时移除时也不弹。
if (!btn.hasAttribute("data-play-coin-open")) return;
if (isPreviewPlayingSurface()) return;
open();
});
});
Array.prototype.forEach.call(closers, function (el) {
el.addEventListener("click", close);
});
document.addEventListener("keydown", function (event) {
if (event.key === "Escape" && root.classList.contains("is-open")) {
close();
}
});
})();
(function () {
var btn = document.querySelector("[data-guess-refresh]");
var grid = document.querySelector("[data-guess-grid]");
if (!btn || !grid) return;
var loading = false;
var PLACEHOLDER = "/static/images/placeholder.png?v=2";
function toInt(v, fallback) {
var n = parseInt(v, 10);
return isNaN(n) ? fallback : n;
}
var videoId = toInt(btn.getAttribute("data-video-id"), 0);
var playletId = toInt(btn.getAttribute("data-playlet-id"), 0);
function nextPage(pageNum, pageSize, totalCount) {
var totalPages =
pageSize > 0 && totalCount > 0 ? Math.ceil(totalCount / pageSize) : 0;
if (totalPages <= 1) return 1;
if (pageNum < 1) pageNum = 1;
var next = pageNum + 1;
return next > totalPages ? 1 : next;
}
function playURL(slug, id) {
var seg = String(slug || "").trim();
if (!seg && id > 0) seg = String(id);
if (!seg) return "#";
return "/play/" + encodeURIComponent(seg);
}
function videoPlayURL(slug, id) {
var seg = String(slug || "").trim();
if (!seg && id > 0) seg = String(id);
if (!seg) return "/series";
return "/series-play/" + encodeURIComponent(seg);
}
function tagPageURL(slug, id) {
var seg = String(slug || "").trim();
if (!seg && id > 0) seg = String(id);
if (!seg) return "";
return "/tags/" + encodeURIComponent(seg);
}
function themeListURL(slug, id) {
var seg = String(slug || "").trim();
if (!seg && id > 0) seg = String(id);
if (!seg) return "/list";
return "/list/" + encodeURIComponent(seg);
}
function pushNamed(out, seen, names, ids, slugs, asTag) {
names = names || [];
ids = ids || [];
slugs = slugs || [];
for (var i = 0; i < names.length && out.length < 3; i++) {
var name = String(names[i] || "").trim();
if (!name) continue;
var key = name.toLowerCase();
if (seen[key]) continue;
seen[key] = true;
var id = ids[i] > 0 ? ids[i] : 0;
var slug = String(slugs[i] || "").trim();
out.push({
name: name,
url: asTag ? tagPageURL(slug, id) : themeListURL(slug, id),
});
}
}
function cardTags(item) {
var out = [];
var seen = {};
pushNamed(out, seen, item.tagNames, item.tagIds, item.tagSlugs, true);
if (out.length < 3) {
pushNamed(out, seen, item.themeNames, item.themeIds, item.themeSlugs, false);
}
return out;
}
function displayName(item) {
return String((item && (item.playName || item.title)) || "").trim() || "短剧";
}
function updateStatusText(status) {
return Number(status) === 2 ? "已完结" : "连载中";
}
function escapeHtml(s) {
return String(s == null ? "" : s)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function renderMeta(item) {
var tags = cardTags(item);
return tags
.map(function (tag, i) {
var sep = i ? " " : "";
var name = escapeHtml(tag.name);
if (tag.url) {
return sep + '<a href="' + escapeHtml(tag.url) + '">' + name + "</a>";
}
return sep + name;
})
.join("");
}
function renderVideoMeta(item) {
var keys = (item && item.keyworld) || [];
var parts = [];
for (var i = 0; i < keys.length && parts.length < 3; i++) {
var k = String(keys[i] || "").trim();
if (k) parts.push(escapeHtml(k));
}
if (parts.length) return parts.join(" ");
return escapeHtml((item && item.creatorName) || "");
}
function renderList(list) {
if (!list || !list.length) {
grid.innerHTML =
'<p class="play-comment__text" style="padding:12px 0;color:var(--muted, #888)">暂无推荐</p>';
return;
}
var html = list
.map(function (item) {
var href = playURL(item.slug, item.id);
var name = escapeHtml(displayName(item));
var meta = renderMeta(item);
var ep =
item.totalEpisode > 0
? "全 " + item.totalEpisode + " 集"
: updateStatusText(item.updateStatus);
var cover = escapeHtml(item.cover || "");
return (
'<article class="card card--play">' +
'<a class="card__cover lazy" href="' +
escapeHtml(href) +
'" style="--cover:url(' +
PLACEHOLDER +
')" data-src="' +
cover +
'">' +
'<span class="card__ep">' +
escapeHtml(ep) +
"</span>" +
"</a>" +
'<h3 class="card__title"><a href="' +
escapeHtml(href) +
'">' +
name +
"</a></h3>" +
'<p class="card__meta">' +
meta +
"</p>" +
"</article>"
);
})
.join("");
grid.innerHTML = html;
if (typeof window.initLazyImages === "function") {
window.initLazyImages(grid);
}
}
function renderVideoList(list) {
if (!list || !list.length) {
grid.innerHTML =
'<p class="play-comment__text" style="padding:12px 0;color:var(--muted, #888)">暂无推荐</p>';
return;
}
var html = list
.map(function (item) {
var href = videoPlayURL(item.slug, item.id);
var name = escapeHtml(String((item && item.title) || "").trim() || "视频");
var meta = renderVideoMeta(item);
var ep =
item.videoTime > 0 ? String(item.videoTime) + " 分钟" : "";
var cover = escapeHtml(item.coverImg || "");
return (
'<article class="card card--play">' +
'<a class="card__cover lazy" href="' +
escapeHtml(href) +
'" style="--cover:url(' +
PLACEHOLDER +
')" data-src="' +
cover +
'">' +
(ep
? '<span class="card__ep">' + escapeHtml(ep) + "</span>"
: "") +
"</a>" +
'<h3 class="card__title"><a href="' +
escapeHtml(href) +
'">' +
name +
"</a></h3>" +
'<p class="card__meta">' +
meta +
"</p>" +
"</article>"
);
})
.join("");
grid.innerHTML = html;
if (typeof window.initLazyImages === "function") {
window.initLazyImages(grid);
}
}
function readVideoRecommendAll() {
var el = document.getElementById("video-guess-like-data");
if (!el) return [];
try {
var raw = JSON.parse(el.textContent || "[]");
return Array.isArray(raw) ? raw : [];
} catch (err) {
return [];
}
}
function slicePage(list, pageNum, pageSize) {
var total = list.length;
if (!total) return { list: [], pageNum: 1, totalCount: 0 };
if (pageNum < 1) pageNum = 1;
var start = (pageNum - 1) * pageSize;
if (start >= total) {
pageNum = 1;
start = 0;
}
return {
list: list.slice(start, start + pageSize),
pageNum: pageNum,
totalCount: total,
};
}
function setBusy(on) {
loading = on;
btn.disabled = !!on;
btn.classList.toggle("is-loading", !!on);
}
btn.addEventListener("click", function () {
if (loading) return;
var page = toInt(btn.getAttribute("data-page"), 1);
var pageSize = toInt(btn.getAttribute("data-page-size"), 6);
var total = toInt(btn.getAttribute("data-total"), 0);
// 视频播放页:详情 recommendVideos 客户端换一批(结构对齐短剧猜你喜欢)
if (videoId > 0) {
var all = readVideoRecommendAll();
var targetPage = nextPage(page, pageSize, all.length || total);
setBusy(true);
try {
var sliced = slicePage(all, targetPage, pageSize);
btn.setAttribute("data-page", String(sliced.pageNum));
btn.setAttribute("data-page-size", String(pageSize));
btn.setAttribute("data-total", String(sliced.totalCount));
renderVideoList(sliced.list);
} finally {
setBusy(false);
}
return;
}
if (!window.HttpRequest || typeof window.HttpRequest.get !== "function") {
return;
}
if (playletId <= 0) return;
var targetPage = nextPage(page, pageSize, total);
setBusy(true);
window.HttpRequest.get("/api/playlet/guess-like/list", {
params: {
playletId: playletId,
pageNum: targetPage,
pageSize: pageSize,
},
})
.then(function (res) {
if (!res || Number(res.code) !== 200 || !res.data) {
throw new Error((res && res.msg) || "guess-like failed");
}
var data = res.data;
var list = data.list || [];
var pageNum = toInt(data.pageNum, targetPage);
var size = toInt(data.pageSize, pageSize);
var totalCount = toInt(data.totalCount, total);
if (!list.length && pageNum > 1) {
return window.HttpRequest.get("/api/playlet/guess-like/list", {
params: {
playletId: playletId,
pageNum: 1,
pageSize: size,
},
}).then(function (retry) {
if (!retry || Number(retry.code) !== 200 || !retry.data) {
throw new Error((retry && retry.msg) || "guess-like failed");
}
return retry.data;
});
}
return data;
})
.then(function (data) {
btn.setAttribute("data-page", String(toInt(data.pageNum, 1)));
btn.setAttribute("data-page-size", String(toInt(data.pageSize, pageSize)));
btn.setAttribute("data-total", String(toInt(data.totalCount, total)));
renderList(data.list || []);
})
.catch(function () {})
.finally(function () {
setBusy(false);
});
});
})();
// 简介展开:PC 超过 1 行、H5 超过 2 行才显示展开按钮
(function () {
var root = document.querySelector("[data-play-synopsis]");
if (!root) return;
var textEl = root.querySelector("[data-play-synopsis-text]");
var moreBtn = root.querySelector("[data-play-synopsis-more]");
var moreLabel = root.querySelector("[data-play-synopsis-more-label]");
if (!textEl || !moreBtn) return;
function isOverflowing() {
var expanded = root.classList.contains("is-expanded");
if (expanded) root.classList.remove("is-expanded");
var needs = textEl.scrollHeight > textEl.clientHeight + 1;
if (expanded) root.classList.add("is-expanded");
return needs;
}
function syncMoreVisibility() {
var needs = isOverflowing();
moreBtn.hidden = !needs;
if (!needs && root.classList.contains("is-expanded")) {
root.classList.remove("is-expanded");
if (moreLabel) moreLabel.textContent = "展开";
moreBtn.setAttribute("aria-expanded", "false");
}
}
moreBtn.addEventListener("click", function () {
var expanded = root.classList.toggle("is-expanded");
if (moreLabel) moreLabel.textContent = expanded ? "收起" : "展开";
moreBtn.setAttribute("aria-expanded", expanded ? "true" : "false");
});
syncMoreVisibility();
window.addEventListener("resize", syncMoreVisibility);
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(syncMoreVisibility).catch(function () {});
}
})();
// 选集:正序 / 倒序(PC tabs + H5 切换共用)
(function () {
var grid = document.querySelector("[data-play-episodes-grid]");
if (!grid) return;
var h5Btn = document.querySelector("[data-play-episodes-order]");
var h5Label = h5Btn && h5Btn.querySelector("[data-play-episodes-order-label]");
var sortItems = document.querySelectorAll("[data-play-episodes-sort-item]");
var desc = false;
function reverseGrid() {
var nodes = Array.prototype.slice.call(grid.children);
if (nodes.length < 2) return;
nodes.reverse().forEach(function (node) {
grid.appendChild(node);
});
}
function syncUI() {
if (h5Btn) {
h5Btn.setAttribute("aria-pressed", desc ? "true" : "false");
h5Btn.classList.toggle("is-desc", desc);
if (h5Label) h5Label.textContent = desc ? "正序" : "倒序";
}
Array.prototype.forEach.call(sortItems, function (item) {
var isDesc = item.getAttribute("data-play-episodes-sort-item") === "desc";
var active = isDesc === desc;
item.classList.toggle("is-active", active);
item.setAttribute("aria-pressed", active ? "true" : "false");
});
}
function setOrder(nextDesc) {
nextDesc = !!nextDesc;
if (nextDesc === desc) return;
desc = nextDesc;
reverseGrid();
syncUI();
}
if (h5Btn) {
h5Btn.addEventListener("click", function () {
setOrder(!desc);
});
}
Array.prototype.forEach.call(sortItems, function (item) {
item.addEventListener("click", function () {
setOrder(item.getAttribute("data-play-episodes-sort-item") === "desc");
});
});
syncUI();
})();