外部播放器增加去广告
This commit is contained in:
11
CHANGELOG
11
CHANGELOG
@@ -1,3 +1,14 @@
|
|||||||
|
## [200.3.0] - 2025-12-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- 增加自定义去广告功能
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 现在外部播放器支持去广告了
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- 修复首页/api/favorites接口重复请求
|
||||||
|
|
||||||
## [200.2.0] - 2025-12-04
|
## [200.2.0] - 2025-12-04
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
214
src/app/api/proxy-m3u8/route.ts
Normal file
214
src/app/api/proxy-m3u8/route.ts
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
import { getConfig } from '@/lib/config';
|
||||||
|
|
||||||
|
export const runtime = 'nodejs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* M3U8 代理接口
|
||||||
|
* 用于外部播放器访问,会执行去广告逻辑并处理相对链接
|
||||||
|
* GET /api/proxy-m3u8?url=<原始m3u8地址>&source=<播放源>
|
||||||
|
*/
|
||||||
|
export async function GET(request: Request) {
|
||||||
|
try {
|
||||||
|
const { searchParams } = new URL(request.url);
|
||||||
|
const m3u8Url = searchParams.get('url');
|
||||||
|
const source = searchParams.get('source') || '';
|
||||||
|
|
||||||
|
if (!m3u8Url) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '缺少必要参数: url' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前请求的 origin
|
||||||
|
const requestUrl = new URL(request.url);
|
||||||
|
const origin = `${requestUrl.protocol}//${requestUrl.host}`;
|
||||||
|
|
||||||
|
// 获取原始 m3u8 内容
|
||||||
|
const response = await fetch(m3u8Url, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '获取 m3u8 文件失败' },
|
||||||
|
{ status: response.status }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let m3u8Content = await response.text();
|
||||||
|
|
||||||
|
// 执行去广告逻辑
|
||||||
|
const config = await getConfig();
|
||||||
|
const customAdFilterCode = config.SiteConfig?.CustomAdFilterCode || '';
|
||||||
|
|
||||||
|
if (customAdFilterCode && customAdFilterCode.trim()) {
|
||||||
|
try {
|
||||||
|
// 移除 TypeScript 类型注解,转换为纯 JavaScript
|
||||||
|
const jsCode = customAdFilterCode
|
||||||
|
.replace(/(\w+)\s*:\s*(string|number|boolean|any|void|never|unknown|object)\s*([,)])/g, '$1$3')
|
||||||
|
.replace(/\)\s*:\s*(string|number|boolean|any|void|never|unknown|object)\s*\{/g, ') {')
|
||||||
|
.replace(/(const|let|var)\s+(\w+)\s*:\s*(string|number|boolean|any|void|never|unknown|object)\s*=/g, '$1 $2 =');
|
||||||
|
|
||||||
|
// 创建并执行自定义函数
|
||||||
|
const customFunction = new Function('type', 'm3u8Content',
|
||||||
|
jsCode + '\nreturn filterAdsFromM3U8(type, m3u8Content);'
|
||||||
|
);
|
||||||
|
m3u8Content = customFunction(source, m3u8Content);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('执行自定义去广告代码失败,使用默认规则:', err);
|
||||||
|
// 继续使用默认规则
|
||||||
|
m3u8Content = filterAdsFromM3U8Default(source, m3u8Content);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 使用默认去广告规则
|
||||||
|
m3u8Content = filterAdsFromM3U8Default(source, m3u8Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 m3u8 中的相对链接
|
||||||
|
m3u8Content = resolveM3u8Links(m3u8Content, m3u8Url, source, origin);
|
||||||
|
|
||||||
|
// 返回处理后的 m3u8 内容
|
||||||
|
return new NextResponse(m3u8Content, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/vnd.apple.mpegurl',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('代理 m3u8 失败:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: '代理失败', details: (error as Error).message },
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认去广告规则
|
||||||
|
*/
|
||||||
|
function filterAdsFromM3U8Default(type: string, m3u8Content: string): string {
|
||||||
|
if (!m3u8Content) return '';
|
||||||
|
|
||||||
|
// 按行分割M3U8内容
|
||||||
|
const lines = m3u8Content.split('\n');
|
||||||
|
const filteredLines = [];
|
||||||
|
|
||||||
|
let nextdelete = false;
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i];
|
||||||
|
|
||||||
|
if (nextdelete) {
|
||||||
|
nextdelete = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只过滤#EXT-X-DISCONTINUITY标识
|
||||||
|
if (!line.includes('#EXT-X-DISCONTINUITY')) {
|
||||||
|
if (
|
||||||
|
type === 'ruyi' &&
|
||||||
|
(line.includes('EXTINF:5.640000') ||
|
||||||
|
line.includes('EXTINF:2.960000') ||
|
||||||
|
line.includes('EXTINF:3.480000') ||
|
||||||
|
line.includes('EXTINF:4.000000') ||
|
||||||
|
line.includes('EXTINF:0.960000') ||
|
||||||
|
line.includes('EXTINF:10.000000') ||
|
||||||
|
line.includes('EXTINF:1.266667'))
|
||||||
|
) {
|
||||||
|
nextdelete = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredLines.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredLines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 m3u8 中的相对链接转换为绝对链接,并将子 m3u8 链接转为代理链接
|
||||||
|
*/
|
||||||
|
function resolveM3u8Links(m3u8Content: string, baseUrl: string, source: string, proxyOrigin: string): string {
|
||||||
|
const lines = m3u8Content.split('\n');
|
||||||
|
const resolvedLines = [];
|
||||||
|
|
||||||
|
// 解析基础URL
|
||||||
|
const base = new URL(baseUrl);
|
||||||
|
const baseDir = base.href.substring(0, base.href.lastIndexOf('/') + 1);
|
||||||
|
|
||||||
|
let isNextLineUrl = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
let line = lines[i];
|
||||||
|
|
||||||
|
// 处理 EXT-X-KEY 标签中的 URI
|
||||||
|
if (line.startsWith('#EXT-X-KEY:')) {
|
||||||
|
// 提取 URI 部分
|
||||||
|
const uriMatch = line.match(/URI="([^"]+)"/);
|
||||||
|
if (uriMatch && uriMatch[1]) {
|
||||||
|
let keyUri = uriMatch[1];
|
||||||
|
|
||||||
|
// 转换为绝对路径
|
||||||
|
if (!keyUri.startsWith('http://') && !keyUri.startsWith('https://')) {
|
||||||
|
if (keyUri.startsWith('/')) {
|
||||||
|
keyUri = `${base.protocol}//${base.host}${keyUri}`;
|
||||||
|
} else {
|
||||||
|
keyUri = new URL(keyUri, baseDir).href;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换原来的 URI
|
||||||
|
line = line.replace(/URI="[^"]+"/, `URI="${keyUri}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolvedLines.push(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注释行直接保留
|
||||||
|
if (line.startsWith('#')) {
|
||||||
|
resolvedLines.push(line);
|
||||||
|
// 检查是否是 EXT-X-STREAM-INF,下一行将是子 m3u8
|
||||||
|
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||||
|
isNextLineUrl = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空行直接保留
|
||||||
|
if (line.trim() === '') {
|
||||||
|
resolvedLines.push(line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 URL 行
|
||||||
|
let url = line.trim();
|
||||||
|
|
||||||
|
// 1. 先转换为绝对 URL
|
||||||
|
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
||||||
|
if (url.startsWith('/')) {
|
||||||
|
// 以 / 开头,相对于域名根目录
|
||||||
|
url = `${base.protocol}//${base.host}${url}`;
|
||||||
|
} else {
|
||||||
|
// 相对于当前目录
|
||||||
|
url = new URL(url, baseDir).href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查是否是子 m3u8,如果是,转换为代理链接
|
||||||
|
const isM3u8 = url.includes('.m3u8') || isNextLineUrl;
|
||||||
|
if (isM3u8) {
|
||||||
|
url = `${proxyOrigin}/api/proxy-m3u8?url=${encodeURIComponent(url)}${source ? `&source=${encodeURIComponent(source)}` : ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedLines.push(url);
|
||||||
|
isNextLineUrl = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedLines.join('\n');
|
||||||
|
}
|
||||||
@@ -112,6 +112,20 @@ function PlayPageClient() {
|
|||||||
blockAdEnabledRef.current = blockAdEnabled;
|
blockAdEnabledRef.current = blockAdEnabled;
|
||||||
}, [blockAdEnabled]);
|
}, [blockAdEnabled]);
|
||||||
|
|
||||||
|
// 外部播放器去广告开关(独立状态,默认 false)
|
||||||
|
const [externalPlayerAdBlock, setExternalPlayerAdBlock] = useState<boolean>(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const v = localStorage.getItem('external_player_adblock');
|
||||||
|
if (v !== null) return v === 'true';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
useEffect(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
localStorage.setItem('external_player_adblock', String(externalPlayerAdBlock));
|
||||||
|
}
|
||||||
|
}, [externalPlayerAdBlock]);
|
||||||
|
|
||||||
// 自定义去广告代码(从服务器获取并缓存)
|
// 自定义去广告代码(从服务器获取并缓存)
|
||||||
const customAdFilterCodeRef = useRef<string>('');
|
const customAdFilterCodeRef = useRef<string>('');
|
||||||
|
|
||||||
@@ -3399,84 +3413,101 @@ function PlayPageClient() {
|
|||||||
{videoUrl && (
|
{videoUrl && (
|
||||||
<div className='mt-3 px-2 lg:flex-shrink-0 flex justify-end'>
|
<div className='mt-3 px-2 lg:flex-shrink-0 flex justify-end'>
|
||||||
<div className='bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-2 border border-gray-200/50 dark:border-gray-700/50 w-full lg:w-auto overflow-x-auto'>
|
<div className='bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm rounded-lg p-2 border border-gray-200/50 dark:border-gray-700/50 w-full lg:w-auto overflow-x-auto'>
|
||||||
<div className='flex gap-1.5 justify-end lg:flex-wrap'>
|
<div className='flex gap-1.5 justify-between lg:flex-wrap items-center'>
|
||||||
{/* 下载按钮 */}
|
<div className='flex gap-1.5 lg:flex-wrap'>
|
||||||
<button
|
{/* 下载按钮 */}
|
||||||
onClick={(e) => {
|
<button
|
||||||
e.preventDefault();
|
onClick={(e) => {
|
||||||
const isM3u8 = videoUrl.toLowerCase().includes('.m3u8') || videoUrl.toLowerCase().includes('/m3u8/');
|
e.preventDefault();
|
||||||
|
// 使用代理 URL
|
||||||
|
const proxyUrl = externalPlayerAdBlock
|
||||||
|
? `${window.location.origin}/api/proxy-m3u8?url=${encodeURIComponent(videoUrl)}&source=${encodeURIComponent(currentSource)}`
|
||||||
|
: videoUrl;
|
||||||
|
const isM3u8 = videoUrl.toLowerCase().includes('.m3u8') || videoUrl.toLowerCase().includes('/m3u8/');
|
||||||
|
|
||||||
if (isM3u8) {
|
if (isM3u8) {
|
||||||
// M3U8格式 - 复制链接并提示
|
// M3U8格式 - 复制链接并提示
|
||||||
navigator.clipboard.writeText(videoUrl).then(() => {
|
navigator.clipboard.writeText(proxyUrl).then(() => {
|
||||||
if (artPlayerRef.current) {
|
if (artPlayerRef.current) {
|
||||||
artPlayerRef.current.notice.show = '链接已复制!请使用 FFmpeg、N_m3u8DL-CLI 或 Downie 等工具下载';
|
artPlayerRef.current.notice.show = externalPlayerAdBlock
|
||||||
}
|
? '代理链接已复制(含去广告)!请使用 FFmpeg、N_m3u8DL-CLI 或 Downie 等工具下载'
|
||||||
}).catch(() => {
|
: '链接已复制!请使用 FFmpeg、N_m3u8DL-CLI 或 Downie 等工具下载';
|
||||||
if (artPlayerRef.current) {
|
}
|
||||||
artPlayerRef.current.notice.show = '复制失败,请手动复制链接';
|
}).catch(() => {
|
||||||
}
|
if (artPlayerRef.current) {
|
||||||
});
|
artPlayerRef.current.notice.show = '复制失败,请手动复制链接';
|
||||||
} else {
|
}
|
||||||
// 普通视频格式 - 直接下载
|
});
|
||||||
const a = document.createElement('a');
|
} else {
|
||||||
a.href = videoUrl;
|
// 普通视频格式 - 直接下载
|
||||||
a.download = `${videoTitle}_第${currentEpisodeIndex + 1}集.mp4`;
|
const a = document.createElement('a');
|
||||||
a.target = '_blank';
|
a.href = proxyUrl;
|
||||||
document.body.appendChild(a);
|
a.download = `${videoTitle}_第${currentEpisodeIndex + 1}集.mp4`;
|
||||||
a.click();
|
a.target = '_blank';
|
||||||
document.body.removeChild(a);
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
|
||||||
if (artPlayerRef.current) {
|
if (artPlayerRef.current) {
|
||||||
artPlayerRef.current.notice.show = '开始下载...';
|
artPlayerRef.current.notice.show = '开始下载...';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}}
|
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-green-400 flex-shrink-0'
|
||||||
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-green-400 flex-shrink-0'
|
title='下载视频'
|
||||||
title='下载视频'
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
className='w-4 h-4 flex-shrink-0 text-white'
|
|
||||||
fill='none'
|
|
||||||
stroke='currentColor'
|
|
||||||
viewBox='0 0 24 24'
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
strokeLinecap='round'
|
className='w-4 h-4 flex-shrink-0 text-white'
|
||||||
strokeLinejoin='round'
|
fill='none'
|
||||||
strokeWidth='2'
|
stroke='currentColor'
|
||||||
d='M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4'
|
viewBox='0 0 24 24'
|
||||||
/>
|
>
|
||||||
</svg>
|
<path
|
||||||
<span className='hidden lg:inline max-w-0 group-hover:max-w-[100px] overflow-hidden whitespace-nowrap transition-all duration-200 ease-in-out text-white'>
|
strokeLinecap='round'
|
||||||
下载
|
strokeLinejoin='round'
|
||||||
</span>
|
strokeWidth='2'
|
||||||
</button>
|
d='M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4'
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className='hidden lg:inline max-w-0 group-hover:max-w-[100px] overflow-hidden whitespace-nowrap transition-all duration-200 ease-in-out text-white'>
|
||||||
|
下载
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* PotPlayer */}
|
{/* PotPlayer */}
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.open(`potplayer://${videoUrl}`, '_blank');
|
// 使用代理 URL
|
||||||
}}
|
const proxyUrl = externalPlayerAdBlock
|
||||||
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-white hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-gray-300 dark:border-gray-600 flex-shrink-0'
|
? `${window.location.origin}/api/proxy-m3u8?url=${encodeURIComponent(videoUrl)}&source=${encodeURIComponent(currentSource)}`
|
||||||
title='PotPlayer'
|
: videoUrl;
|
||||||
>
|
// URL encode 避免冒号被吃掉
|
||||||
<img
|
window.open(`potplayer://${proxyUrl}`, '_blank');
|
||||||
src='/players/potplayer.png'
|
}}
|
||||||
alt='PotPlayer'
|
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-white hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-gray-300 dark:border-gray-600 flex-shrink-0'
|
||||||
className='w-4 h-4 flex-shrink-0'
|
title='PotPlayer'
|
||||||
/>
|
>
|
||||||
<span className='hidden lg:inline max-w-0 group-hover:max-w-[100px] overflow-hidden whitespace-nowrap transition-all duration-200 ease-in-out text-gray-700 dark:text-gray-200'>
|
<img
|
||||||
PotPlayer
|
src='/players/potplayer.png'
|
||||||
</span>
|
alt='PotPlayer'
|
||||||
</button>
|
className='w-4 h-4 flex-shrink-0'
|
||||||
|
/>
|
||||||
|
<span className='hidden lg:inline max-w-0 group-hover:max-w-[100px] overflow-hidden whitespace-nowrap transition-all duration-200 ease-in-out text-gray-700 dark:text-gray-200'>
|
||||||
|
PotPlayer
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* VLC */}
|
{/* VLC */}
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.open(`vlc://${videoUrl}`, '_blank');
|
// 使用代理 URL
|
||||||
|
const proxyUrl = externalPlayerAdBlock
|
||||||
|
? `${window.location.origin}/api/proxy-m3u8?url=${encodeURIComponent(videoUrl)}&source=${encodeURIComponent(currentSource)}`
|
||||||
|
: videoUrl;
|
||||||
|
// URL encode 避免冒号被吃掉
|
||||||
|
window.open(`vlc://${proxyUrl}`, '_blank');
|
||||||
}}
|
}}
|
||||||
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-white hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-gray-300 dark:border-gray-600 flex-shrink-0'
|
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-white hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-gray-300 dark:border-gray-600 flex-shrink-0'
|
||||||
title='VLC'
|
title='VLC'
|
||||||
@@ -3495,7 +3526,12 @@ function PlayPageClient() {
|
|||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.open(`mpv://${videoUrl}`, '_blank');
|
// 使用代理 URL
|
||||||
|
const proxyUrl = externalPlayerAdBlock
|
||||||
|
? `${window.location.origin}/api/proxy-m3u8?url=${encodeURIComponent(videoUrl)}&source=${encodeURIComponent(currentSource)}`
|
||||||
|
: videoUrl;
|
||||||
|
// URL encode 避免冒号被吃掉
|
||||||
|
window.open(`mpv://${proxyUrl}`, '_blank');
|
||||||
}}
|
}}
|
||||||
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-white hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-gray-300 dark:border-gray-600 flex-shrink-0'
|
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-white hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-gray-300 dark:border-gray-600 flex-shrink-0'
|
||||||
title='MPV'
|
title='MPV'
|
||||||
@@ -3514,11 +3550,12 @@ function PlayPageClient() {
|
|||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
// 使用代理 URL
|
||||||
|
const proxyUrl = externalPlayerAdBlock
|
||||||
|
? `${window.location.origin}/api/proxy-m3u8?url=${encodeURIComponent(videoUrl)}&source=${encodeURIComponent(currentSource)}`
|
||||||
|
: videoUrl;
|
||||||
window.open(
|
window.open(
|
||||||
`intent://${videoUrl.replace(
|
`intent://${proxyUrl}#Intent;package=com.mxtech.videoplayer.ad;S.title=${encodeURIComponent(
|
||||||
/^https?:\/\//,
|
|
||||||
''
|
|
||||||
)}#Intent;package=com.mxtech.videoplayer.ad;S.title=${encodeURIComponent(
|
|
||||||
videoTitle
|
videoTitle
|
||||||
)};end`,
|
)};end`,
|
||||||
'_blank'
|
'_blank'
|
||||||
@@ -3541,7 +3578,11 @@ function PlayPageClient() {
|
|||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.open(`nplayer-${videoUrl}`, '_blank');
|
// 使用代理 URL
|
||||||
|
const proxyUrl = externalPlayerAdBlock
|
||||||
|
? `${window.location.origin}/api/proxy-m3u8?url=${encodeURIComponent(videoUrl)}&source=${encodeURIComponent(currentSource)}`
|
||||||
|
: videoUrl;
|
||||||
|
window.open(`nplayer-${proxyUrl}`, '_blank');
|
||||||
}}
|
}}
|
||||||
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-white hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-gray-300 dark:border-gray-600 flex-shrink-0'
|
className='group relative flex items-center justify-center gap-1 w-8 h-8 lg:w-auto lg:h-auto lg:px-2 lg:py-1.5 bg-white hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 text-xs font-medium rounded-md transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer overflow-hidden border border-gray-300 dark:border-gray-600 flex-shrink-0'
|
||||||
title='nPlayer'
|
title='nPlayer'
|
||||||
@@ -3560,9 +3601,13 @@ function PlayPageClient() {
|
|||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
// 使用代理 URL
|
||||||
|
const proxyUrl = externalPlayerAdBlock
|
||||||
|
? `${window.location.origin}/api/proxy-m3u8?url=${encodeURIComponent(videoUrl)}&source=${encodeURIComponent(currentSource)}`
|
||||||
|
: videoUrl;
|
||||||
window.open(
|
window.open(
|
||||||
`iina://weblink?url=${encodeURIComponent(
|
`iina://weblink?url=${encodeURIComponent(
|
||||||
videoUrl
|
proxyUrl
|
||||||
)}`,
|
)}`,
|
||||||
'_blank'
|
'_blank'
|
||||||
);
|
);
|
||||||
@@ -3579,6 +3624,44 @@ function PlayPageClient() {
|
|||||||
IINA
|
IINA
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 去广告开关 */}
|
||||||
|
<button
|
||||||
|
onClick={() => setExternalPlayerAdBlock(!externalPlayerAdBlock)}
|
||||||
|
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-md text-xs font-medium transition-all duration-200 shadow-sm hover:shadow-md cursor-pointer border flex-shrink-0 ${
|
||||||
|
externalPlayerAdBlock
|
||||||
|
? 'bg-gradient-to-r from-blue-500 to-indigo-600 hover:from-blue-600 hover:to-indigo-700 text-white border-blue-400'
|
||||||
|
: 'bg-white hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 border-gray-300 dark:border-gray-600'
|
||||||
|
}`}
|
||||||
|
title={externalPlayerAdBlock ? '去广告已开启' : '去广告已关闭'}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className='w-4 h-4 flex-shrink-0'
|
||||||
|
fill='none'
|
||||||
|
stroke='currentColor'
|
||||||
|
viewBox='0 0 24 24'
|
||||||
|
>
|
||||||
|
{externalPlayerAdBlock ? (
|
||||||
|
<path
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
strokeWidth='2'
|
||||||
|
d='M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<path
|
||||||
|
strokeLinecap='round'
|
||||||
|
strokeLinejoin='round'
|
||||||
|
strokeWidth='2'
|
||||||
|
d='M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
<span className='whitespace-nowrap'>
|
||||||
|
{externalPlayerAdBlock ? '去广告' : '去广告'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,6 +10,17 @@ export interface ChangelogEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const changelog: ChangelogEntry[] = [
|
export const changelog: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
version: '200.3.0',
|
||||||
|
date: '2025-12-05',
|
||||||
|
added: [
|
||||||
|
'增加自定义去广告功能'
|
||||||
|
],
|
||||||
|
changed: [
|
||||||
|
'现在外部播放器支持去广告了'
|
||||||
|
],
|
||||||
|
fixed: ['修复首页/api/favorites接口重复请求'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: '200.2.0',
|
version: '200.2.0',
|
||||||
date: '2025-12-04',
|
date: '2025-12-04',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
const CURRENT_VERSION = '200.2.0';
|
const CURRENT_VERSION = '200.3.0';
|
||||||
|
|
||||||
// 导出当前版本号供其他地方使用
|
// 导出当前版本号供其他地方使用
|
||||||
export { CURRENT_VERSION };
|
export { CURRENT_VERSION };
|
||||||
|
|||||||
@@ -133,6 +133,6 @@ function shouldSkipAuth(pathname: string): boolean {
|
|||||||
// 配置middleware匹配规则
|
// 配置middleware匹配规则
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: [
|
matcher: [
|
||||||
'/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config).*)',
|
'/((?!_next/static|_next/image|favicon.ico|login|warning|api/login|api/register|api/logout|api/cron|api/server-config|api/proxy-m3u8).*)',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user