集成anime4k
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@upstash/redis": "^1.25.0",
|
||||
"@vidstack/react": "^1.12.13",
|
||||
"anime4k-webgpu": "^1.0.0",
|
||||
"artplayer": "^5.2.5",
|
||||
"bs58": "^6.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
|
||||
33
pnpm-lock.yaml
generated
33
pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ importers:
|
||||
'@vidstack/react':
|
||||
specifier: ^1.12.13
|
||||
version: 1.12.13(@types/react@18.3.23)(react@18.3.1)
|
||||
anime4k-webgpu:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0(@webgpu/types@0.1.66)
|
||||
artplayer:
|
||||
specifier: ^5.2.5
|
||||
version: 5.2.5
|
||||
@@ -1797,6 +1800,9 @@ packages:
|
||||
'@webassemblyjs/wast-printer@1.14.1':
|
||||
resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==}
|
||||
|
||||
'@webgpu/types@0.1.66':
|
||||
resolution: {integrity: sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==}
|
||||
|
||||
'@xtuc/ieee754@1.2.0':
|
||||
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
||||
|
||||
@@ -1878,6 +1884,11 @@ packages:
|
||||
resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==}
|
||||
engines: {node: '>=0.4.2'}
|
||||
|
||||
anime4k-webgpu@1.0.0:
|
||||
resolution: {integrity: sha512-syZZyDRHYukFnDLxdES4AuMHzKwxlu5Lo52yhEEc1hwKQmvza8DGC62/VDPrrWOu4KArU5jh/0yFJQBwetGuxg==}
|
||||
peerDependencies:
|
||||
'@webgpu/types': ^0.1.38
|
||||
|
||||
ansi-escapes@4.3.2:
|
||||
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7285,6 +7296,8 @@ snapshots:
|
||||
'@webassemblyjs/ast': 1.14.1
|
||||
'@xtuc/long': 4.2.2
|
||||
|
||||
'@webgpu/types@0.1.66': {}
|
||||
|
||||
'@xtuc/ieee754@1.2.0': {}
|
||||
|
||||
'@xtuc/long@4.2.2': {}
|
||||
@@ -7357,6 +7370,10 @@ snapshots:
|
||||
|
||||
amdefine@1.0.1: {}
|
||||
|
||||
anime4k-webgpu@1.0.0(@webgpu/types@0.1.66):
|
||||
dependencies:
|
||||
'@webgpu/types': 0.1.66
|
||||
|
||||
ansi-escapes@4.3.2:
|
||||
dependencies:
|
||||
type-fest: 0.21.3
|
||||
@@ -8220,8 +8237,8 @@ snapshots:
|
||||
'@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
|
||||
eslint-plugin-react: 7.37.5(eslint@8.57.1)
|
||||
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1)
|
||||
@@ -8244,7 +8261,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1):
|
||||
dependencies:
|
||||
'@nolyfill/is-core-module': 1.0.39
|
||||
debug: 4.4.1(supports-color@9.4.0)
|
||||
@@ -8255,22 +8272,22 @@ snapshots:
|
||||
tinyglobby: 0.2.14
|
||||
unrs-resolver: 1.9.0
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@@ -8281,7 +8298,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.57.1
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
|
||||
@@ -7,6 +7,7 @@ import Hls from 'hls.js';
|
||||
import { Heart } from 'lucide-react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { Suspense, useEffect, useRef, useState } from 'react';
|
||||
import { Anime4K } from 'anime4k-webgpu';
|
||||
|
||||
import {
|
||||
deleteFavorite,
|
||||
@@ -96,6 +97,58 @@ function PlayPageClient() {
|
||||
blockAdEnabledRef.current = blockAdEnabled;
|
||||
}, [blockAdEnabled]);
|
||||
|
||||
// Anime4K超分相关状态
|
||||
const [webGPUSupported, setWebGPUSupported] = useState<boolean>(false);
|
||||
const [anime4kEnabled, setAnime4kEnabled] = useState<boolean>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const v = localStorage.getItem('enable_anime4k');
|
||||
if (v !== null) return v === 'true';
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const [anime4kMode, setAnime4kMode] = useState<string>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const v = localStorage.getItem('anime4k_mode');
|
||||
if (v !== null) return v;
|
||||
}
|
||||
return 'fast';
|
||||
});
|
||||
const anime4kRef = useRef<any>(null);
|
||||
const anime4kEnabledRef = useRef(anime4kEnabled);
|
||||
const anime4kModeRef = useRef(anime4kMode);
|
||||
useEffect(() => {
|
||||
anime4kEnabledRef.current = anime4kEnabled;
|
||||
anime4kModeRef.current = anime4kMode;
|
||||
}, [anime4kEnabled, anime4kMode]);
|
||||
|
||||
// 检测WebGPU支持
|
||||
useEffect(() => {
|
||||
const checkWebGPUSupport = async () => {
|
||||
if (typeof navigator === 'undefined' || !('gpu' in navigator)) {
|
||||
setWebGPUSupported(false);
|
||||
console.log('WebGPU不支持:浏览器不支持WebGPU API');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const adapter = await (navigator as any).gpu.requestAdapter();
|
||||
if (!adapter) {
|
||||
setWebGPUSupported(false);
|
||||
console.log('WebGPU不支持:无法获取GPU适配器');
|
||||
return;
|
||||
}
|
||||
|
||||
setWebGPUSupported(true);
|
||||
console.log('WebGPU支持检测:✅ 支持');
|
||||
} catch (err) {
|
||||
setWebGPUSupported(false);
|
||||
console.log('WebGPU不支持:', err);
|
||||
}
|
||||
};
|
||||
|
||||
checkWebGPUSupport();
|
||||
}, []);
|
||||
|
||||
// 视频基本信息
|
||||
const [videoTitle, setVideoTitle] = useState(searchParams.get('title') || '');
|
||||
const [videoYear, setVideoYear] = useState(searchParams.get('year') || '');
|
||||
@@ -495,6 +548,106 @@ function PlayPageClient() {
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化Anime4K超分
|
||||
const initAnime4K = async () => {
|
||||
if (!artPlayerRef.current?.video) return;
|
||||
|
||||
try {
|
||||
if (anime4kRef.current) {
|
||||
await anime4kRef.current.destroy();
|
||||
anime4kRef.current = null;
|
||||
}
|
||||
|
||||
const video = artPlayerRef.current.video as HTMLVideoElement;
|
||||
const canvas = document.createElement('canvas');
|
||||
const container = artPlayerRef.current.template.$video.parentElement;
|
||||
|
||||
// 设置canvas样式
|
||||
canvas.style.position = 'absolute';
|
||||
canvas.style.top = '0';
|
||||
canvas.style.left = '0';
|
||||
canvas.style.width = '100%';
|
||||
canvas.style.height = '100%';
|
||||
canvas.style.objectFit = 'contain';
|
||||
|
||||
// 隐藏原始video元素
|
||||
video.style.display = 'none';
|
||||
|
||||
// 插入canvas到容器
|
||||
container.insertBefore(canvas, video);
|
||||
|
||||
// 初始化Anime4K
|
||||
const anime4k = new Anime4K({
|
||||
video: video,
|
||||
canvas: canvas,
|
||||
mode: anime4kModeRef.current as any,
|
||||
sharpness: 1.0,
|
||||
});
|
||||
|
||||
await anime4k.init();
|
||||
anime4kRef.current = anime4k;
|
||||
|
||||
console.log('Anime4K超分已启用,模式:', anime4kModeRef.current);
|
||||
if (artPlayerRef.current) {
|
||||
artPlayerRef.current.notice.show = `超分已启用 (${anime4kModeRef.current})`;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('初始化Anime4K失败:', err);
|
||||
if (artPlayerRef.current) {
|
||||
artPlayerRef.current.notice.show = '超分启用失败:' + (err instanceof Error ? err.message : '未知错误');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 清理Anime4K
|
||||
const cleanupAnime4K = async () => {
|
||||
if (anime4kRef.current) {
|
||||
try {
|
||||
await anime4kRef.current.destroy();
|
||||
anime4kRef.current = null;
|
||||
|
||||
// 恢复原始video显示
|
||||
if (artPlayerRef.current?.video) {
|
||||
artPlayerRef.current.video.style.display = 'block';
|
||||
}
|
||||
|
||||
console.log('Anime4K已清理');
|
||||
} catch (err) {
|
||||
console.warn('清理Anime4K时出错:', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 切换Anime4K状态
|
||||
const toggleAnime4K = async (enabled: boolean) => {
|
||||
try {
|
||||
if (enabled) {
|
||||
await initAnime4K();
|
||||
} else {
|
||||
await cleanupAnime4K();
|
||||
}
|
||||
setAnime4kEnabled(enabled);
|
||||
localStorage.setItem('enable_anime4k', String(enabled));
|
||||
} catch (err) {
|
||||
console.error('切换超分状态失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// 更改Anime4K模式
|
||||
const changeAnime4KMode = async (mode: string) => {
|
||||
try {
|
||||
setAnime4kMode(mode);
|
||||
localStorage.setItem('anime4k_mode', mode);
|
||||
|
||||
if (anime4kEnabledRef.current) {
|
||||
await cleanupAnime4K();
|
||||
await initAnime4K();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('更改超分模式失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
function filterAdsFromM3U8(type: string, m3u8Content: string): string {
|
||||
if (!m3u8Content) return '';
|
||||
|
||||
@@ -1430,6 +1583,44 @@ function PlayPageClient() {
|
||||
return newVal ? '当前开启' : '当前关闭';
|
||||
},
|
||||
},
|
||||
...(webGPUSupported ? [
|
||||
{
|
||||
name: 'Anime4K超分',
|
||||
html: 'Anime4K超分',
|
||||
icon: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 2L2 7v10c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V7l-10-5zm0 18c-4 0-7-3-7-7V9l7-3.5L19 9v4c0 4-3 7-7 7z" fill="#ffffff"/><path d="M10 12l2 2 4-4" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
switch: anime4kEnabledRef.current,
|
||||
onSwitch: async function (item: any) {
|
||||
const newVal = !item.switch;
|
||||
await toggleAnime4K(newVal);
|
||||
return newVal;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '超分模式',
|
||||
html: '超分模式',
|
||||
selector: [
|
||||
{
|
||||
html: '快速模式',
|
||||
value: 'fast',
|
||||
default: anime4kModeRef.current === 'fast',
|
||||
},
|
||||
{
|
||||
html: '平衡模式',
|
||||
value: 'balanced',
|
||||
default: anime4kModeRef.current === 'balanced',
|
||||
},
|
||||
{
|
||||
html: '高质量模式',
|
||||
value: 'high_quality',
|
||||
default: anime4kModeRef.current === 'high_quality',
|
||||
},
|
||||
],
|
||||
onSelect: async function (item: any) {
|
||||
await changeAnime4KMode(item.value);
|
||||
return item.html;
|
||||
},
|
||||
}
|
||||
] : []),
|
||||
{
|
||||
name: '跳过片头片尾',
|
||||
html: '跳过片头片尾',
|
||||
@@ -1514,13 +1705,18 @@ function PlayPageClient() {
|
||||
});
|
||||
|
||||
// 监听播放器事件
|
||||
artPlayerRef.current.on('ready', () => {
|
||||
artPlayerRef.current.on('ready', async () => {
|
||||
setError(null);
|
||||
|
||||
// 播放器就绪后,如果正在播放则请求 Wake Lock
|
||||
if (artPlayerRef.current && !artPlayerRef.current.paused) {
|
||||
requestWakeLock();
|
||||
}
|
||||
|
||||
// 如果启用了Anime4K,初始化超分
|
||||
if (anime4kEnabledRef.current) {
|
||||
await initAnime4K();
|
||||
}
|
||||
});
|
||||
|
||||
// 监听播放状态变化,控制 Wake Lock
|
||||
@@ -1689,6 +1885,9 @@ function PlayPageClient() {
|
||||
// 释放 Wake Lock
|
||||
releaseWakeLock();
|
||||
|
||||
// 清理Anime4K
|
||||
cleanupAnime4K();
|
||||
|
||||
// 销毁播放器实例
|
||||
cleanupPlayer();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user