Files
SEONexus/code/public/public/video/common.js
2025-11-29 22:53:25 +08:00

643 lines
20 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
// 无限的 debugger 兼容性好
// setInterval(function () {
// debuggerCheck();
// }, 1000);
// var debuggerCheck = function () {
// function doCheck(a) {
// if (('' + a / a)['length'] !== 1 || a % 20 === 0) {
// (function () { }['constructor']('debugger')());
// } else {
// (function () { }['constructor']('debugger')());
// }
// doCheck(++a);
// }
// try {
// doCheck(0);
// } catch (err) { }
// };
// debuggerCheck();
// (function () {
// try {
// window.history.pushState(null, '');
// window.addEventListener(
// 'popstate',
// function () {
// window.location.href = atob('aHR0cHM6Ly9zczQwLnNnY2l6dC5jb206MTA4My80MC8=');
// },
// false
// );
// } catch (err) { }
// })();
function formatVideoUrl(strPinYin, intVId) {
return strVideoUrlTemp
.replace('{strPinYin}', encodeURIComponent(strPinYin))
.replace('{intVId}', intVId);
}
function formatVideoPlayUrl(strPinYin, intVId, strPlayType = 'defualt', intPlayIndex = 1) {
return strVideoPlayUrlTemp
.replace('{strPinYin}', encodeURIComponent(strPinYin))
.replace('{intVId}', intVId)
.replace('{strPlayType}', strPlayType)
.replace('{intPlayIndex}', intPlayIndex);
}
function formatSearchUrl(strKey, intPage = 1) {
return strSearchUrlTemp
.replace('{strSearchKeywords}', strKey)
.replace('{intPage}', intPage);
}
/**
* 获取当前页面的顶级域名
* @returns {string} - 顶级域名(如 'video.com', 'example.org'),若无法解析则返回空字符串
*/
function getTopLevelDomain() {
try {
const hostname = window.location.hostname; // 获取完整域名,如 'sub.video.com'
const parts = hostname.split('.'); // 按点号分割域名
// 返回最后两部分作为顶级域名(适用于大多数情况,如 'video.com'
return parts.length >= 2 ? `${parts[parts.length - 2]}.${parts[parts.length - 1]}` : '';
} catch (error) {
console.error('获取顶级域名失败:', error.message);
return '';
}
}
function isMobile() {
const ua = navigator.userAgent || navigator.vendor || window.opera;
// 常见移动端关键词
const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Windows Phone/i;
// 微信/QQ/支付宝内置浏览器也算移动端
const isMiniBrowser = /MicroMessenger|QQ\/|AlipayClient/i.test(ua);
// 判断触控点Touch device
const isTouchDevice = ('maxTouchPoints' in navigator && navigator.maxTouchPoints > 0) ||
('ontouchstart' in window);
return mobileRegex.test(ua) || isMiniBrowser || isTouchDevice;
}
// 懒加载
document.addEventListener("DOMContentLoaded", function () {
// 通用懒加载函数
function lazyLoad(elements, callback) {
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const element = entry.target;
callback(element);
observer.unobserve(element);
}
});
}, {
rootMargin: '100px' // 提前 100px 开始加载
});
elements.forEach(element => observer.observe(element));
}
// 背景封面懒加载
const lazyBackgrounds = document.querySelectorAll(".lazyload-bg");
lazyLoad(lazyBackgrounds, (element) => {
const src = element.getAttribute("data-src");
if (src) {
const img = new Image();
img.src = src;
img.onload = () => {
element.style.backgroundImage = `url(${src})`;
element.classList.remove("lazyload-bg");
};
img.onerror = () => {
// console.error(`Failed to load background image: ${src}`);
// 可选:设置默认背景或错误提示
};
}
});
// 章节图片懒加载
const lazyImages = document.querySelectorAll(".lazyload-img");
lazyLoad(lazyImages, (element) => {
const src = element.getAttribute("data-src");
if (src) {
const img = new Image();
img.src = src;
img.onload = () => {
element.setAttribute("src", src);
element.classList.remove("lazyload-img");
};
img.onerror = () => {
// console.error(`Failed to load image: ${src}`);
// 可选:设置占位图或错误提示
};
}
});
});
function handleRedirect() {
const { pathname, origin, search } = window.location;
// 规范化路径名,将多个斜杠替换为单个斜杠
const normalizedPath = pathname.replace(/\/+/g, '/');
var strCurrentPath = normalizedPath || '/index.html';
const arrPcPathUrl = ['/pc', '/web', '/desktop', '/index', '/shouye', '/zhuomian', '/windows']; // 所有模板的 PC 目录
// 构建URL确保斜杠正确
const buildUrl = (path) => {
const cleanPath = `/${path.replace(/\/+/g, '/')}`.replace(/^\/+/, '/');
return `${origin}${cleanPath}${search}`;
};
// 获取当前路径的第一个目录
var firstSegment = strCurrentPath.split('/')[1] || '';
// 判断当前路径的第一个目录是否在其它模板的 PC 目录中
const isInPcPathArray = arrPcPathUrl.some(pcPath => firstSegment.startsWith(pcPath.replace('/', '')));
if (isMobile()) {
// 如果第一个目录 在pc 模板里面 则删除替换成空
if (isInPcPathArray) {
const newPath = strCurrentPath.replace(firstSegment, '');
window.location.href = buildUrl(newPath);
}
} else {
if (!isInPcPathArray) {
const newPath = strCurrentPath === '/' ? `/` : `${strCurrentPath}`;
window.location.href = buildUrl(newPath);
} else {
// 如果包含,则先判断是不是当前模板的 url如果不是 则替换成当前模板的 url
if (firstSegment != strPcPath.replace('/', '')) {
strCurrentPath = strCurrentPath.replace(firstSegment, strPcPath.replace('/', ''));
window.location.href = buildUrl(strCurrentPath);
}
}
}
}
// handleRedirect();
function layerPopup(strContent, intTime) {
layer.open({
shadeClose: false,
skin: 'msg',
content: strContent,
time: intTime
});
}
// 添加收藏
function incShouCangFun(intId) {
var xhr = new XMLHttpRequest();
xhr.open('GET', "/sc?intId=" + intId, true);
xhr.setRequestHeader('accept', 'application/x.hubserver.admin+json');
xhr.send();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
layerPopup('收藏成功', 3)
}
if (xhr.readyState == 4 && xhr.status == 0) {
layerPopup('操作太频繁,稍后再试', 3)
}
}
}
const strBookshelfKey = window.location.hostname + 'strBookshelfKey';
const strHistoryKey = window.location.hostname + 'strHistoryKey';
// 加入书架
function addBookshelfFun(NovelInfo) {
if (!NovelInfo) {
NovelInfo = arrNovel
}
let arrBookshelf = JSON.parse(localStorage.getItem(strBookshelfKey)) || [];
// 检查书籍是否已存在
const index = arrBookshelf.findIndex(item => item.n_id === NovelInfo.n_id);
if (index !== -1) {
// 如果存在,更新该书籍的信息
arrBookshelf[index] = { ...arrBookshelf[index], ...NovelInfo };
layerPopup('已存在书架中!', 3)
} else {
// 如果不存在,添加到历史记录
arrBookshelf.push(NovelInfo);
layerPopup('已成功加入书架!', 3)
}
// 更新本地存储
localStorage.setItem(strBookshelfKey, JSON.stringify(arrBookshelf));
}
function displayShujia() {
// 获取书架数据
const shujia = JSON.parse(localStorage.getItem(strBookshelfKey)) || [];
if (shujia.length === 0) {
console.log('书架为空');
return;
}
// 渲染书籍列表
const shujiaContainer = document.getElementById('shujiaContainer');
shujiaContainer.innerHTML = ''; // 清空原内容
shujia.forEach(book => {
const bookElement = document.createElement('div');
let strNovelUrl = formatNovelUrl(book.n_name_pinyin, book.n_id)
let strNovelChapterUrl = formatNovelChapterUrl(book.n_name_pinyin, book.n_id, book.c_sort_num)
let strSearchUrl = formatSearchUrl(book.n_author, 1)
if (isMobile()) {
bookElement.innerHTML = `
<dl class="rec-focus-book">
<dt class="book-img">
<a href='${strNovelUrl}'>
<img height="100" width="80" src="${book.n_cover_path}" >
</a>
</dt>
<dd class="book-info">
<h2><a href='${strNovelUrl}'>${book.n_name}</a></h2>
<p>作者: ${book.n_author}</p>
<p> ${book.c_name}</p>
</dd>
</dl>
`;
} else {
bookElement.innerHTML = `
<div class="item">
<div class="image">
<a href='${strNovelUrl}'>
<img style="min-height:120px;" src="${book.n_cover_path}" alt="${book.n_name}">
</a>
</div>
<dl>
<dt>
<span>${book.n_author}</span>
<a href='${strNovelUrl}'>${book.n_name}</a>
</dt>
<dd style="height:90px">
<a href='${strNovelChapterUrl}'
style="color: #555">${book.c_name}</a>
</dd>
</dl>
</div>
`;
}
shujiaContainer.appendChild(bookElement);
});
}
// 加入阅读记录
function addHistoryFun(VideoInfo) {
// 如果没有传入 VideoInfo arrVideo 并补充字段
if (!VideoInfo) {
VideoInfo = arrVideo
}
// 获取本地存储中的历史记录
let arrHistory = JSON.parse(localStorage.getItem(strHistoryKey)) || [];
// 检查书籍是否已存在
const index = arrHistory.findIndex(item => item.v_id == VideoInfo.v_id);
if (index !== -1) {
// 如果存在,更新该书籍的信息
arrHistory[index] = { ...arrHistory[index], ...VideoInfo };
} else {
// 如果不存在,添加到历史记录
arrHistory.push(VideoInfo);
}
// 更新本地存储
localStorage.setItem(strHistoryKey, JSON.stringify(arrHistory));
}
const defaultTemplate = `
<li class="active clearfix">
<div class="thumb">
<a class="v-thumb stui-vodlist__thumb lazyload-bg"
href="{{videoUrl}}"
title="{{videoName}}"
data-src="{{videoPic}}">
<span class="play hidden-xs"></span>
<span class="pic-text text-right">{{videoRemarks}}</span>
</a>
</div>
<div class="detail">
<h3 class="title">
<a href="{{videoUrl}}">{{videoName}}</a>
</h3>
<p class="hidden-mi">
<span class="text-muted">简介:</span>
{{videoDescription}}
</p>
<p class="hidden-mi">
<span class="text-muted">类型:</span>
{{videoCategory}}
<span class="split-line"></span>
<span class="hidden-xs">
<span class="split-line"></span>
<span class="text-muted">年份:</span>
{{videoYear}}
</span>
</p>
<p class="margin-0">
<a class="btn btn-min btn-primary" href="{{videoPlayUrl}}">立即播放</a>
  
<a class="btn btn-min btn-default" href="{{videoUrl}}">查看详情</a>
</p>
</div>
</li>
`;
// 渲染历史记录函数,接受自定义模板参数
function displayHistory(template = defaultTemplate) {
// 获取数据
const arrHistory = JSON.parse(localStorage.getItem(strHistoryKey)) || [];
if (arrHistory.length === 0) {
console.log('记录为空');
return;
}
// 渲染列表
const historyContainer = document.getElementById('historyContainer');
if (!historyContainer) {
console.error('历史记录容器未找到');
return;
}
historyContainer.innerHTML = ''; // 清空原内容
arrHistory.forEach((video) => {
// 生成URL
const strVideoUrl = formatVideoUrl(video.v_name_en, video.v_id);
const strVideoPlayUrl = formatVideoPlayUrl(video.v_name_en, video.v_id, 'default', 1);
const strSearchUrl = formatSearchUrl(video.v_name, 1);
// 替换模板中的占位符
const renderedTemplate = template
.replace(/{{videoUrl}}/g, strVideoUrl)
.replace(/{{videoName}}/g, escapeHtml(video.v_name)) // 防止XSS
.replace(/{{videoPic}}/g, escapeHtml(video.v_pic))
.replace(/{{videoRemarks}}/g, escapeHtml(video.v_remarks))
.replace(/{{videoDescription}}/g, escapeHtml(video.v_description))
.replace(/{{videoCategory}}/g, escapeHtml(video.v_category))
.replace(/{{videoParentCategory}}/g, escapeHtml(video.v_parent_category))
.replace(/{{videoYear}}/g, escapeHtml(video.v_year))
.replace(/{{videoActor}}/g, escapeHtml(video.v_area))
.replace(/{{videoPlayUrl}}/g, strVideoPlayUrl)
.replace(/{{searchUrl}}/g, strSearchUrl);
// 创建元素并添加到容器
const videoElement = document.createElement('li');
videoElement.innerHTML = renderedTemplate;
historyContainer.appendChild(videoElement);
});
}
// 防止XSS攻击的转义函数
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>"']/g, (match) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}[match]));
}
async function reportStats(data) {
try {
const response = await fetch(strPushApi + '/publishv', {
method: 'POST',
// mode: 'no-cors', // 添加这行
headers: {
'Content-Type': 'application/json',
// 如果需要认证可以添加
'Authorization': 'Bearer your-token'
},
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error('上报失败');
}
} catch (error) {
console.error('上报错误:', error);
}
}
function initDPlayer(strPlayUrl) {
let DomDPlayer = document.getElementById('dplayer')
videoPlayer = new DPlayer({
container: DomDPlayer,
autoplay: true,
screenshot: false,
//logo: dplayerLogo,
video: {
url: strPlayUrl,
type: 'hls',
},
pluginOptions: {
hls: {
maxBufferLength: 600,
},
},
});
videoPlayer.play()
// 监听全屏进入事件
videoPlayer.on('fullscreen', function () {
DomDPlayer.classList.add("dp-fullscreen");
// 设置视频元素的样式,取消 max-height
document.querySelector("video").style.maxHeight = "none";
document.querySelector(".video-play-page .dplayer-video-wrap").style.maxHeight = "none";
});
// 监听全屏退出事件
videoPlayer.on('fullscreen_cancel', function () {
DomDPlayer.classList.remove("dp-fullscreen");
// 恢复 max-height 为 480px
if (isMobile()) {
document.querySelector("video").style.maxHeight = "218px";
document.querySelector(".video-play-page .dplayer-video-wrap").style.maxHeight = "218px";
} else {
document.querySelector("video").style.maxHeight = strMaxPlayHeight ?? '480px';
document.querySelector(".video-play-page .dplayer-video-wrap").style.maxHeight = strMaxPlayHeight ?? '480px';
}
});
videoPlayer.on('error', function () {
});
videoPlayer.on('play', function () {
});
// 监听当前播放时间
videoPlayer.on('timeupdate', function () {
});
}
function findFirstUrl(obj) {
// 优先取 默认
// if (Array.isArray(obj?.douban) && obj.douban.length > 0 && obj.douban[0].url) {
// return obj.douban[0].url;
// }
// 如果没有 douban尝试取对象中第一个数组的第一个 url
for (const key in obj) {
if (Array.isArray(obj[key]) && obj[key].length > 0 && obj[key][0].url) {
return obj[key];
// return obj[key][0].url;
}
}
return null;
}
// 切换播放线路
// 切换播放线路
function checkLine(val) {
// 构造 ID
let strLineHeadId = `line_head_${val}`;
let strLineListId = `playlist_${val}`;
// 获取目标元素
let headElement = document.getElementById(strLineHeadId);
let listElement = document.getElementById(strLineListId);
// 获取所有头部导航和播放列表
let navItems = document.querySelectorAll('.play-group-head');
let tabPanes = document.querySelectorAll('.play-group-list');
// ✅ 如果目标元素不存在,则默认选中第一个
if (!headElement || !listElement) {
if (navItems.length > 0 && tabPanes.length > 0) {
headElement = navItems[0];
listElement = tabPanes[0];
} else {
console.warn('没有可用的播放线路元素');
return;
}
}
// 1. 更新头部导航 active 状态
navItems.forEach(item => {
item.classList.toggle('active', item === headElement);
});
// 2. 更新播放列表 active 状态
tabPanes.forEach(pane => {
pane.classList.toggle('active', pane === listElement);
});
}
function reorderObj(obj) {
if (!obj || typeof obj !== 'object') return obj;
let newObj = {};
if (obj.hasOwnProperty('douban')) {
newObj['douban'] = obj['douban'];
}
for (let key in obj) {
if (key !== 'douban' && obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
document.addEventListener("DOMContentLoaded", function () {
if (typeof strVideoId !== "undefined") {
// if (typeof arrPlayUrl !== "undefined" && arrPlayUrl) {
// arrPlayUrl = reorderObj(arrPlayUrl);
// }
// 使用示例
const statsData = {
v_id: strVideoId,
};
// 调用方法1
reportStats(statsData);
// if (typeof strPlayType !== "undefined" && boolIsPlayPage) {
// if (strPlayType == 'default') {
// strPlayUrl = findFirstUrl(arrPlayUrl)
// checkLine('douban');
// } else {
// var ActivePlayUrl = arrPlayUrl[strPlayType][intPlayUrlIndex - 1]
// strPlayUrl = ActivePlayUrl.url
// checkLine(strPlayType);
// }
// initDPlayer(strPlayUrl)
// }else if(strPlayType == "default" && !boolIsPlayPage){
// checkLine('douban');
// }
if (typeof strPlayType !== "undefined" && boolIsPlayPage) {
// 用户指定线路
if (strPlayType !== "default") {
const activeUrl = arrPlayUrl[strPlayType][intPlayUrlIndex - 1];
strPlayUrl = activeUrl.url;
checkLine(strPlayType);
// 默认播放第一个线路的第一个 URL
} else {
const defaultLine = getDefaultLine();
strPlayUrl = getFirstPlayUrl(defaultLine);
checkLine(defaultLine);
}
initDPlayer(strPlayUrl);
} else if (strPlayType == "default" && !boolIsPlayPage) {
const defaultLine = getDefaultLine();
checkLine(defaultLine);
}
addHistoryFun();
}
// 获取第一个线路 key
function getDefaultLine() {
return Object.keys(arrPlayUrl)[0]; // 后端已经排序,第一位永远是默认线路
}
// 获取某个线路的第一个 url
function getFirstPlayUrl(playLine) {
return arrPlayUrl[playLine][0].url;
}
})