修复预览视频播放 更新文档

This commit is contained in:
lzyme.dev@gmail.com
2026-08-30 15:57:27 +08:00
parent 42bc3f624a
commit fb5120551c
11 changed files with 1901 additions and 133 deletions
+17 -7
View File
@@ -1,10 +1,12 @@
import { BasePlugin } from '../core/base-plugin.js';
import { __publicField, qualityOptions } from '../core/constants.js';
import {getDmmVideo, selectDefaultQuality} from '../api/dmm.js';
class BusPreviewVideoPlugin extends BasePlugin {
getName() {
return "BusPreviewVideoPlugin";
}
async initCss() {
return "\n /* 弹窗/Modal 样式 */\n .bus-preview-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.95); \n /* 关键修改:更新 z-index */\n z-index: 12345699; \n display: flex;\n justify-content: center;\n align-items: center;\n opacity: 0; \n visibility: hidden; \n transition: opacity 0.2s ease;\n }\n .bus-preview-modal.is-open {\n opacity: 1;\n visibility: visible;\n }\n /* 垂直排列视频和按钮,并居中 */\n .bus-preview-modal-content {\n position: relative;\n max-width: 95%; \n max-height: 95%;\n display: flex; \n flex-direction: column; \n align-items: center; \n gap: 15px; \n }\n \n /* 移除 .bus-preview-close-btn 的样式 */\n\n /* 视频播放器容器 */\n .video-player-wrapper {\n /* 关键修改:更新 width 和 max-height */\n width: 80vw; \n max-height: 85vh; \n aspect-ratio: 16 / 9; \n position: relative; \n background-color: black; \n max-width: 100%; \n }\n /* 视频元素 */\n .video-player-wrapper #preview-video {\n position: absolute; \n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: block;\n }\n\n /* 画质控制盒 (底部按钮) */\n .video-control-box {\n display: flex;\n flex-direction: row; \n justify-content: center; \n flex-wrap: wrap; \n gap: 10px;\n padding: 10px 0; \n }\n\n /* 按钮样式 (保留) */\n .video-control-btn {\n min-width:80px;\n padding: 6px 12px;\n background: rgba(255,255,255,0.2);\n color: white;\n border: 1px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n cursor: pointer;\n text-align: center;\n font-size: 14px;\n transition: background-color 0.2s, border-color 0.2s;\n }\n .video-control-btn:hover {\n background: rgba(255,255,255,0.4);\n }\n .video-control-btn.active {\n background-color: #1890ff; \n color: white;\n font-weight: bold;\n border: 1px solid #096dd9;\n }\n ";
}
@@ -22,7 +24,10 @@ class BusPreviewVideoPlugin extends BasePlugin {
}
closeVideoModal() {
const $previewVideo = $("#preview-video");
$previewVideo.length > 0 && $previewVideo[0].pause();
if ($previewVideo.length > 0) {
const videoEl = $previewVideo[0];
videoEl.pause();
}
$("#bus-preview-modal").removeClass("is-open");
}
async handle() {
@@ -51,7 +56,8 @@ class BusPreviewVideoPlugin extends BasePlugin {
let $previewVideo = $("#preview-video");
if ($previewVideo.length > 0) {
$modal.addClass("is-open");
$previewVideo[0].play().catch((e => console.warn("尝试播放失败 (可能被浏览器阻止):", e)));
const videoEl = $previewVideo[0];
videoEl.play().catch((e => console.warn("尝试播放失败 (可能被浏览器阻止):", e)));
return;
}
let carNum2 = this.getPageInfo().carNum;
@@ -70,13 +76,15 @@ class BusPreviewVideoPlugin extends BasePlugin {
defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), defaultVideoQuality);
let defaultVideoUrl = dmmVideoMap[defaultVideoQuality];
$container.html(`\n <div class="video-player-wrapper">\n <video id="preview-video" controls playsinline>\n <source src="${defaultVideoUrl}" />\n </video>\n </div>\n <div class="video-control-box">\n </div>\n `);
const $videoEl = $("#preview-video"), $previewSource = $videoEl.find("source"), $qualityControlsBox = $container.find(".video-control-box");
if (!$videoEl.length || !$previewSource.length) return;
const videoEl = $videoEl[0], jhs_videoMuted = localStorage.getItem("jhs_videoMuted");
const $videoEl = $("#preview-video"), $qualityControlsBox = $container.find(".video-control-box");
if (!$videoEl.length) return;
const videoEl = $videoEl[0];
const jhs_videoMuted = localStorage.getItem("jhs_videoMuted");
videoEl.muted = !jhs_videoMuted || "yes" === jhs_videoMuted;
videoEl.addEventListener("volumechange", (function() {
localStorage.setItem("jhs_videoMuted", videoEl.muted ? "yes" : "no");
}));
let buttonsHtml = "";
qualityOptions.forEach((option => {
let dmmVideoUrl = dmmVideoMap[option.quality];
@@ -92,10 +100,12 @@ class BusPreviewVideoPlugin extends BasePlugin {
const $button = $(e.currentTarget);
if ($button.hasClass("active")) return;
let videoSrc = $button.attr("data-video-src");
$previewSource.attr("src", videoSrc);
const currentTime = videoEl.currentTime;
videoEl.src = videoSrc;
videoEl.load();
videoEl.currentTime = currentTime;
if (currentTime > 0 && Number.isFinite(currentTime)) {
videoEl.currentTime = currentTime;
}
await videoEl.play();
$buttons.removeClass("active");
$button.addClass("active");
+239
View File
@@ -0,0 +1,239 @@
import {BasePlugin} from '../core/base-plugin.js';
import {__publicField, isJavBus, isJavDb, qualityOptions} from '../core/constants.js';
import {isM3U8Url, attachHlsToVideo, destroyHls, loadHlsLibrary, getHlsClass} from '../lib/hls-runtime.js';
import {getJavxyVideoUrls} from '../api/javxy.js';
class JavxyPreviewVideoPlugin extends BasePlugin {
getName() {
return "JavxyPreviewVideoPlugin";
}
_attachVideoSrc(videoEl, src) {
if (!videoEl || !src) return;
destroyHls(videoEl);
if (isM3U8Url(src)) {
const HlsClass = getHlsClass();
if (HlsClass) {
attachHlsToVideo(videoEl, src).catch((err) => {
clog.error('视频预览 HLS 加载失败:', err);
videoEl.src = src;
videoEl.load && videoEl.load();
});
} else {
loadHlsLibrary().then((HlsClass) => {
if (videoEl && videoEl.isConnected && HlsClass) {
attachHlsToVideo(videoEl, src).catch((err) => {
clog.error('视频预览 HLS 延迟加载失败:', err);
videoEl.src = src;
videoEl.load && videoEl.load();
});
} else if (videoEl && videoEl.isConnected) {
videoEl.src = src;
videoEl.load && videoEl.load();
}
});
}
} else {
videoEl.src = src;
videoEl.load && videoEl.load();
}
}
_selectDefaultQuality(qualityList, intendedDefault) {
if (!qualityList || qualityList.length === 0) return null;
const availableSet = new Set(qualityList);
if (availableSet.has(intendedDefault)) return intendedDefault;
const priorityOrder = qualityOptions.map(o => o.quality).reverse();
for (const q of priorityOrder) if (availableSet.has(q)) return q;
return qualityList[0];
}
async initCss() {
return `
.jhs-javxy-video-modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.95); z-index: 12345700;
display: flex; justify-content: center; align-items: center;
opacity: 0; visibility: hidden; transition: opacity 0.2s;
}
.jhs-javxy-video-modal.is-open { opacity: 1; visibility: visible; }
.jhs-javxy-video-modal-inner {
display: flex; flex-direction: column; align-items: center;
gap: 12px; max-width: 90vw; max-height: 90vh;
}
.jhs-javxy-video-wrapper {
width: 80vw; max-height: 80vh; aspect-ratio: 16/9;
background: #000; position: relative;
}
.jhs-javxy-video-wrapper video {
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
}
.jhs-javxy-quality-bar {
display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;
}
.jhs-javxy-quality-btn {
min-width: 60px; padding: 5px 10px; font-size: 13px;
background: rgba(255,255,255,0.2); color: #fff;
border: 1px solid rgba(255,255,255,0.5); border-radius: 4px;
cursor: pointer; transition: background 0.2s;
}
.jhs-javxy-quality-btn:hover { background: rgba(255,255,255,0.4); }
.jhs-javxy-quality-btn.active {
background: #1890ff; border-color: #096dd9; font-weight: bold;
}
.jhs-javxy-loading {
color: #fff; font-size: 16px; text-align: center;
}
`;
}
async handle() {
if (!window.isDetailPage) return;
const carNum = this.getPageInfo().carNum;
if (!carNum) return;
this._addJavxyButton(carNum);
}
_addJavxyButton(carNum) {
let $target = null;
if (isJavDb) {
$target = $('a[title="複製番號"]');
} else if (isJavBus) {
const headerSpans = document.querySelectorAll("span.header");
for (const span of headerSpans) {
if (span.textContent.trim() === "識別碼:") {
const nextSpan = span.nextElementSibling;
if (nextSpan && nextSpan.tagName === "SPAN") {
const copyBtn = nextSpan.nextElementSibling;
if (copyBtn && copyBtn.tagName === "BUTTON" && copyBtn.textContent.trim() === "复制") {
$target = $(copyBtn);
} else {
$target = $(nextSpan);
}
}
break;
}
}
}
if (!$target || !$target.length) return;
const $btn = $(`<a class="site-btn" style="min-width: auto; padding: 0 5px; margin-left: 6px; margin-bottom: 0; background-color: #1890ff; vertical-align: middle; cursor: pointer; font-size: 11px;"><span>视频预览</span></a>`);
$btn.on("click", async (e) => {
e.preventDefault();
e.stopPropagation();
$btn.css("pointer-events", "none").find("span").text("...");
try {
await this._openJavxyPlayer(carNum);
} finally {
$btn.css("pointer-events", "").find("span").text("视频预览");
}
});
$target.after($btn);
}
async _openJavxyPlayer(carNum) {
if ($("#jhs-javxy-video-modal").length === 0) {
$("body").append(`
<div id="jhs-javxy-video-modal" class="jhs-javxy-video-modal">
<div class="jhs-javxy-video-modal-inner">
<div class="jhs-javxy-video-wrapper">
<video id="jhs-javxy-video" controls playsinline></video>
</div>
<div class="jhs-javxy-quality-bar"></div>
</div>
</div>
`);
const $modal = $("#jhs-javxy-video-modal");
$modal.on("click", (e) => {
if (e.target.id === "jhs-javxy-video-modal") {
$modal.removeClass("is-open");
const videoEl = document.getElementById("jhs-javxy-video");
if (videoEl) {
destroyHls(videoEl);
videoEl.pause();
}
}
});
$(document).on("keydown", (e) => {
if (e.key === "Escape" && $modal.hasClass("is-open")) {
$modal.removeClass("is-open");
const videoEl = document.getElementById("jhs-javxy-video");
if (videoEl) {
destroyHls(videoEl);
videoEl.pause();
}
}
});
}
const $modal = $("#jhs-javxy-video-modal");
const $wrapper = $modal.find(".jhs-javxy-video-wrapper");
const $qualityBar = $modal.find(".jhs-javxy-quality-bar");
$wrapper.html('<div class="jhs-javxy-loading">视频预览加载中...</div>');
$qualityBar.empty();
$modal.addClass("is-open");
let videoMap = null;
try {
videoMap = await getJavxyVideoUrls(carNum);
} catch (err) {
$wrapper.html(`<div class="jhs-javxy-loading">视频预览请求失败: ${err.message}</div>`);
return;
}
if (!videoMap || Object.keys(videoMap).length === 0) {
$wrapper.html('<div class="jhs-javxy-loading">未找到可用的视频源</div>');
return;
}
const qualityList = Object.keys(videoMap);
const defaultQuality = this._selectDefaultQuality(qualityList, '720p');
const defaultUrl = videoMap[defaultQuality];
$wrapper.html(`<video id="jhs-javxy-video" controls playsinline>
<source src="${defaultUrl}" />
</video>`);
const videoEl = document.getElementById("jhs-javxy-video");
if (!videoEl) return;
if (isM3U8Url(defaultUrl)) {
this._attachVideoSrc(videoEl, defaultUrl);
}
let buttonsHtml = "";
qualityOptions.forEach((option) => {
const url = videoMap[option.quality];
if (url) {
buttonsHtml += `<button class="jhs-javxy-quality-btn${option.quality === defaultQuality ? ' active' : ''}"
data-quality="${option.quality}" data-video-src="${url}">${option.text}</button>`;
}
});
$qualityBar.html(buttonsHtml);
$qualityBar.off("click").on("click", ".jhs-javxy-quality-btn", (e) => {
const $btn = $(e.currentTarget);
if ($btn.hasClass("active")) return;
const src = $btn.attr("data-video-src");
const currentTime = videoEl.currentTime;
destroyHls(videoEl);
this._attachVideoSrc(videoEl, src);
videoEl.load();
if (currentTime > 0 && Number.isFinite(currentTime)) {
videoEl.currentTime = currentTime;
}
videoEl.play().catch(() => {
});
$qualityBar.find(".jhs-javxy-quality-btn").removeClass("active");
$btn.addClass("active");
});
videoEl.play().catch((e) => console.warn("视频预览播放失败:", e));
}
}
export {JavxyPreviewVideoPlugin};
+17 -5
View File
@@ -1,5 +1,6 @@
import { BasePlugin } from '../core/base-plugin.js';
import { __publicField, currentHref, NO, YES, qualityOptions } from '../core/constants.js';
import {getDmmVideo, selectDefaultQuality} from '../api/dmm.js';
class PreviewVideoPlugin extends BasePlugin {
getName() {
@@ -49,7 +50,7 @@ class PreviewVideoPlugin extends BasePlugin {
} else {
clog.debug("JavDB没有视频播放元素, 开始创建...");
const videoCoverSrc = $(".column-video-cover img").attr("src");
$(".preview-images").prepend(`\n <a class="preview-video-container" data-fancybox="gallery" href="#preview-video">\n <span>預告片</span>\n <img src="${videoCoverSrc}" class="video-cover" style="width: 150px; height: auto;" alt="">\n </a>\n `);
$(".preview-images").prepend(`\n <a class="preview-video-container" data-fancybox="gallery" href="#preview-video">\n <span>预告片</span>\n <img src="${videoCoverSrc}" class="video-cover" style="width: 150px; height: auto;" alt="">\n </a>\n `);
$(".preview-video-container").on("click", (event => {
utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (async () => {
await this.handleVideo();
@@ -60,18 +61,22 @@ class PreviewVideoPlugin extends BasePlugin {
clog.error("预加载dmm失败:", error);
}
}
async handleVideo() {
if (await storageManager.getSetting("enableLoadPreviewVideo", YES) === NO) return;
const $videoEl = $("#preview-video");
if (!$videoEl.length) return;
const $videoContainer = $videoEl.parent();
$videoContainer.css("position", "relative");
const videoEl = $videoEl[0], jhs_videoMuted = localStorage.getItem("jhs_videoMuted");
jhs_videoMuted && (videoEl.muted = "yes" === jhs_videoMuted);
const videoEl = $videoEl[0];
let carNum2 = this.getPageInfo().carNum;
const jhs_videoMuted = localStorage.getItem("jhs_videoMuted");
if (jhs_videoMuted) videoEl.muted = "yes" === jhs_videoMuted;
videoEl.addEventListener("volumechange", (function() {
localStorage.setItem("jhs_videoMuted", videoEl.muted ? "yes" : "no");
}));
let carNum2 = this.getPageInfo().carNum;
const CACHE_KEY = "jhs_dmm_video";
let dmmErrorRetryCount = 0;
const attachVideoErrorHandler = () => {
@@ -90,13 +95,18 @@ class PreviewVideoPlugin extends BasePlugin {
const storedQuality = await storageManager.getSetting("videoQuality");
const quality = selectDefaultQuality(Object.keys(freshMap), storedQuality);
const freshUrl = freshMap[quality];
const savedTime = videoEl.currentTime;
$videoEl.attr("src", freshUrl);
videoEl.load();
if (savedTime > 0 && Number.isFinite(savedTime)) {
videoEl.currentTime = savedTime;
}
videoEl.play();
}, {once: false});
};
attachVideoErrorHandler();
videoEl.play();
const dmmVideoMap = await getDmmVideo(carNum2);
let $bottomToolbar = $("<div></div>").attr("id", "video-bottom-toolbar").css({
display: "flex",
@@ -146,7 +156,9 @@ class PreviewVideoPlugin extends BasePlugin {
const currentTime = videoEl.currentTime;
$videoEl.attr("src", videoSrc);
videoEl.load();
videoEl.currentTime = currentTime;
if (currentTime > 0 && Number.isFinite(currentTime)) {
videoEl.currentTime = currentTime;
}
await videoEl.play();
$bottomToolbar.find(".video-control-btn").removeClass("active").css({
"background-color": "#fff",