Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1409 lines
52 KiB
JavaScript
1409 lines
52 KiB
JavaScript
(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 (
|
||
'<button class="play-speed-sheet-option" type="button" role="option" data-play-speed-option="' +
|
||
rate +
|
||
'" data-play-speed-index="' +
|
||
index +
|
||
'">' +
|
||
'<span class="play-speed-sheet-option-label">' +
|
||
label +
|
||
"</span>" +
|
||
'<img class="play-speed-sheet-check" src="/static/images/check_pink.svg" alt="已选择" data-play-speed-check hidden />' +
|
||
"</button>"
|
||
);
|
||
})
|
||
.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 =
|
||
'<img src="/static/images/figma-play/control-next.svg" alt="" aria-hidden="true">';
|
||
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 = '<img src="/static/images/figma-play/control-center-play.svg" alt="播放" />';
|
||
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();
|
||
}
|
||
})();
|