mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-14 00:03:42 +08:00
feat: Add comprehensive danmaku settings UI and store integration, and refactor danmaku props from player control components.
This commit is contained in:
@@ -111,6 +111,15 @@
|
||||
- **自定义关键词**:支持通过环境变量或文件扩展过滤关键词
|
||||
- **高性能**:基于流式处理,对播放加载速度几乎无影响
|
||||
|
||||
### 弹幕 (Danmaku)
|
||||
|
||||
- **弹幕聚合**:接入自建弹幕聚合 API(兼容 [danmu_api](https://github.com/huangxd-/danmu_api) 格式),自动匹配当前播放内容
|
||||
- **Canvas 渲染**:基于 Canvas 的高性能弹幕渲染,支持数百条弹幕同时滚动,不影响播放交互
|
||||
- **滚动/顶部/底部弹幕**:支持三种弹幕类型,自动分配轨道防止重叠
|
||||
- **自定义显示**:可调节弹幕透明度(10%-100%)和字号(14/18/20/24/28px)
|
||||
- **播放器联动**:暂停时弹幕冻结,跳转时自动清除,全屏模式下正常显示
|
||||
- **环境变量预设**:可通过 `NEXT_PUBLIC_DANMAKU_API_URL` 为所有用户预设弹幕 API 地址
|
||||
|
||||
### 数据管理
|
||||
|
||||
- **设置导出/导入**:支持将所有设置导出为 JSON 文件,方便备份和迁移
|
||||
@@ -272,6 +281,27 @@ docker run -d -p 3000:3000 \
|
||||
--name kvideo kuekhaoyang/kvideo:latest
|
||||
```
|
||||
|
||||
## 弹幕 API 配置
|
||||
|
||||
通过环境变量预设弹幕聚合 API 地址,用户无需手动配置即可使用弹幕功能。
|
||||
|
||||
| 变量名 | 说明 | 默认值 |
|
||||
|--------|------|--------|
|
||||
| `NEXT_PUBLIC_DANMAKU_API_URL` | 弹幕聚合 API 地址 | - |
|
||||
|
||||
需要自建或使用兼容 [danmu_api](https://github.com/huangxd-/danmu_api) 格式的弹幕聚合服务。
|
||||
|
||||
**示例:**
|
||||
|
||||
```bash
|
||||
# Docker
|
||||
docker run -d -p 3000:3000 \
|
||||
-e NEXT_PUBLIC_DANMAKU_API_URL="https://your-danmu-api.example.com" \
|
||||
--name kvideo kuekhaoyang/kvideo:latest
|
||||
```
|
||||
|
||||
设置后用户在播放器菜单中即可直接开启弹幕,也可在设置页面中覆盖此地址。
|
||||
|
||||
## 自定义源 JSON 格式
|
||||
|
||||
如果你想创建自己的订阅源或批量导入源,可以使用以下 JSON 格式。
|
||||
@@ -346,6 +376,7 @@ docker run -d -p 3000:3000 \
|
||||
| `NEXT_PUBLIC_SUBSCRIPTION_SOURCES` | 自动订阅源配置(客户端) | - |
|
||||
| `AD_KEYWORDS` / `NEXT_PUBLIC_AD_KEYWORDS` | 广告过滤关键词 | - |
|
||||
| `AD_KEYWORDS_FILE` | 广告关键词文件路径 | - |
|
||||
| `NEXT_PUBLIC_DANMAKU_API_URL` | 弹幕聚合 API 地址 | - |
|
||||
|
||||
## 技术栈
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ export function useSettingsPage() {
|
||||
const [proxyMode, setProxyMode] = useState<ProxyMode>('retry');
|
||||
const [rememberScrollPosition, setRememberScrollPosition] = useState(true);
|
||||
|
||||
// Danmaku settings
|
||||
const [danmakuApiUrl, setDanmakuApiUrl] = useState('');
|
||||
const [danmakuOpacity, setDanmakuOpacity] = useState(0.7);
|
||||
const [danmakuFontSize, setDanmakuFontSize] = useState(20);
|
||||
|
||||
useEffect(() => {
|
||||
const settings = settingsStore.getSettings();
|
||||
setSources(settings.sources || []);
|
||||
@@ -36,6 +41,9 @@ export function useSettingsPage() {
|
||||
setFullscreenType(settings.fullscreenType);
|
||||
setProxyMode(settings.proxyMode);
|
||||
setRememberScrollPosition(settings.rememberScrollPosition);
|
||||
setDanmakuApiUrl(settings.danmakuApiUrl);
|
||||
setDanmakuOpacity(settings.danmakuOpacity);
|
||||
setDanmakuFontSize(settings.danmakuFontSize);
|
||||
}, []);
|
||||
|
||||
const handleSourcesChange = (newSources: VideoSource[]) => {
|
||||
@@ -246,6 +254,34 @@ export function useSettingsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleDanmakuApiUrlChange = (url: string) => {
|
||||
setDanmakuApiUrl(url);
|
||||
const currentSettings = settingsStore.getSettings();
|
||||
settingsStore.saveSettings({
|
||||
...currentSettings,
|
||||
danmakuApiUrl: url,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDanmakuOpacityChange = (value: number) => {
|
||||
const clamped = Math.max(0.1, Math.min(1, value));
|
||||
setDanmakuOpacity(clamped);
|
||||
const currentSettings = settingsStore.getSettings();
|
||||
settingsStore.saveSettings({
|
||||
...currentSettings,
|
||||
danmakuOpacity: clamped,
|
||||
});
|
||||
};
|
||||
|
||||
const handleDanmakuFontSizeChange = (value: number) => {
|
||||
setDanmakuFontSize(value);
|
||||
const currentSettings = settingsStore.getSettings();
|
||||
settingsStore.saveSettings({
|
||||
...currentSettings,
|
||||
danmakuFontSize: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRestoreDefaults = () => {
|
||||
const defaults = getDefaultSources();
|
||||
handleSourcesChange(defaults);
|
||||
@@ -296,5 +332,11 @@ export function useSettingsPage() {
|
||||
handleProxyModeChange,
|
||||
rememberScrollPosition,
|
||||
handleRememberScrollPositionChange,
|
||||
danmakuApiUrl,
|
||||
handleDanmakuApiUrlChange,
|
||||
danmakuOpacity,
|
||||
handleDanmakuOpacityChange,
|
||||
danmakuFontSize,
|
||||
handleDanmakuFontSizeChange,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -53,6 +53,12 @@ export default function SettingsPage() {
|
||||
handleProxyModeChange,
|
||||
rememberScrollPosition,
|
||||
handleRememberScrollPositionChange,
|
||||
danmakuApiUrl,
|
||||
handleDanmakuApiUrlChange,
|
||||
danmakuOpacity,
|
||||
handleDanmakuOpacityChange,
|
||||
danmakuFontSize,
|
||||
handleDanmakuFontSizeChange,
|
||||
} = useSettingsPage();
|
||||
|
||||
return (
|
||||
@@ -71,6 +77,12 @@ export default function SettingsPage() {
|
||||
onFullscreenTypeChange={handleFullscreenTypeChange}
|
||||
proxyMode={proxyMode}
|
||||
onProxyModeChange={handleProxyModeChange}
|
||||
danmakuApiUrl={danmakuApiUrl}
|
||||
onDanmakuApiUrlChange={handleDanmakuApiUrlChange}
|
||||
danmakuOpacity={danmakuOpacity}
|
||||
onDanmakuOpacityChange={handleDanmakuOpacityChange}
|
||||
danmakuFontSize={danmakuFontSize}
|
||||
onDanmakuFontSizeChange={handleDanmakuFontSizeChange}
|
||||
/>
|
||||
|
||||
{/* Display Settings */}
|
||||
|
||||
@@ -278,8 +278,6 @@ export function DesktopVideoPlayer({
|
||||
actions={actions}
|
||||
logic={logic}
|
||||
refs={refs}
|
||||
danmakuEnabled={danmakuEnabled}
|
||||
onToggleDanmaku={() => setDanmakuEnabled(!danmakuEnabled)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,8 +32,6 @@ interface DesktopControlsProps {
|
||||
onProgressMouseDown: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||
onProgressTouchStart: (e: React.TouchEvent<HTMLDivElement>) => void;
|
||||
formatTime: (seconds: number) => string;
|
||||
danmakuEnabled?: boolean;
|
||||
onToggleDanmaku?: () => void;
|
||||
}
|
||||
|
||||
export function DesktopControls(props: DesktopControlsProps) {
|
||||
|
||||
@@ -9,11 +9,9 @@ interface DesktopControlsWrapperProps {
|
||||
actions: ReturnType<typeof useDesktopPlayerState>['actions'];
|
||||
logic: ReturnType<typeof useDesktopPlayerLogic>;
|
||||
refs: ReturnType<typeof useDesktopPlayerState>['refs'];
|
||||
danmakuEnabled?: boolean;
|
||||
onToggleDanmaku?: () => void;
|
||||
}
|
||||
|
||||
export function DesktopControlsWrapper({ src, data, actions, logic, refs, danmakuEnabled, onToggleDanmaku }: DesktopControlsWrapperProps) {
|
||||
export function DesktopControlsWrapper({ src, data, actions, logic, refs }: DesktopControlsWrapperProps) {
|
||||
const {
|
||||
isPlaying,
|
||||
currentTime,
|
||||
@@ -78,8 +76,6 @@ export function DesktopControlsWrapper({ src, data, actions, logic, refs, danmak
|
||||
onProgressMouseDown={handleProgressMouseDown}
|
||||
onProgressTouchStart={handleProgressTouchStart}
|
||||
formatTime={formatTime}
|
||||
danmakuEnabled={danmakuEnabled}
|
||||
onToggleDanmaku={onToggleDanmaku}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,12 +47,9 @@ export function DesktopMoreMenu({
|
||||
setAdFilterMode,
|
||||
fullscreenType,
|
||||
setFullscreenType,
|
||||
danmakuEnabled,
|
||||
setDanmakuEnabled,
|
||||
danmakuApiUrl,
|
||||
setDanmakuApiUrl,
|
||||
danmakuOpacity,
|
||||
setDanmakuOpacity,
|
||||
danmakuFontSize,
|
||||
setDanmakuFontSize,
|
||||
} = usePlayerSettings();
|
||||
|
||||
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
||||
@@ -367,57 +364,31 @@ export function DesktopMoreMenu({
|
||||
{/* Divider */}
|
||||
<div className="h-px bg-[var(--glass-border)] my-1.5 sm:my-2" />
|
||||
|
||||
{/* Danmaku API URL */}
|
||||
<div className={`${isRotated ? 'px-2 py-1.5' : 'px-3 py-2 sm:px-4 sm:py-2.5'}`}>
|
||||
<div className={`flex items-center gap-2 text-[var(--text-color)] ${isRotated ? 'text-[11px]' : 'text-xs sm:text-sm'} mb-1.5`}>
|
||||
<Icons.Danmaku size={isRotated ? 14 : 16} className="sm:w-[18px] sm:h-[18px]" />
|
||||
<span>弹幕 API</span>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://example.com"
|
||||
value={danmakuApiUrl}
|
||||
onChange={(e) => setDanmakuApiUrl(e.target.value)}
|
||||
className={`w-full bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] text-[var(--text-color)] placeholder:text-[var(--text-color-secondary)]/50 focus:outline-none focus:border-[var(--accent-color)] ${isRotated ? 'px-2 py-0.5 text-[10px]' : 'px-2.5 py-1 sm:px-3 sm:py-1.5 text-xs sm:text-sm'}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Danmaku Opacity Slider */}
|
||||
<div className={`${isRotated ? 'px-2 py-1.5' : 'px-3 py-2 sm:px-4 sm:py-2.5'} flex items-center justify-between gap-3`}>
|
||||
<div className={`flex items-center gap-2 text-[var(--text-color)] ${isRotated ? 'text-[11px]' : 'text-xs sm:text-sm'} whitespace-nowrap`}>
|
||||
<span>透明度</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
<input
|
||||
type="range"
|
||||
min="10"
|
||||
max="100"
|
||||
value={Math.round(danmakuOpacity * 100)}
|
||||
onChange={(e) => setDanmakuOpacity(parseInt(e.target.value) / 100)}
|
||||
className={`flex-1 accent-[var(--accent-color)] ${isRotated ? 'h-1' : 'h-1.5'}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<span className={`text-[var(--text-color-secondary)] ${isRotated ? 'text-[9px]' : 'text-[10px] sm:text-xs'} w-7 text-right`}>
|
||||
{Math.round(danmakuOpacity * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Danmaku Font Size */}
|
||||
{/* Danmaku Toggle */}
|
||||
<div className={`${isRotated ? 'px-2 py-1.5' : 'px-3 py-2 sm:px-4 sm:py-2.5'} flex items-center justify-between gap-4`}>
|
||||
<div className={`flex items-center gap-2 text-[var(--text-color)] ${isRotated ? 'text-[11px]' : 'text-xs sm:text-sm'}`}>
|
||||
<span>字号</span>
|
||||
<div className={`flex items-center gap-2 ${!danmakuApiUrl ? 'text-[var(--text-color-secondary)]' : 'text-[var(--text-color)]'} ${isRotated ? 'text-[11px]' : 'text-xs sm:text-sm'}`}>
|
||||
<Icons.Danmaku size={isRotated ? 14 : 16} className="sm:w-[18px] sm:h-[18px]" />
|
||||
<span>弹幕</span>
|
||||
{!danmakuApiUrl && (
|
||||
<span className={`${isRotated ? 'text-[9px]' : 'text-[10px] sm:text-xs'} text-[var(--text-color-secondary)]`}>(未配置)</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
const sizes = [14, 18, 20, 24, 28];
|
||||
const idx = sizes.indexOf(danmakuFontSize);
|
||||
setDanmakuFontSize(sizes[(idx + 1) % sizes.length]);
|
||||
}}
|
||||
className={`flex items-center gap-1 bg-[var(--glass-bg)] border border-[var(--glass-border)] text-[var(--text-color)] rounded-[var(--radius-2xl)] outline-none hover:border-[var(--accent-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_5%,transparent)] transition-all cursor-pointer whitespace-nowrap ${isRotated ? 'px-1.5 py-0.5 text-[9px]' : 'px-2 sm:px-2.5 py-1 sm:py-1.5 text-[10px] sm:text-xs'}`}
|
||||
onClick={() => danmakuApiUrl && setDanmakuEnabled(!danmakuEnabled)}
|
||||
disabled={!danmakuApiUrl}
|
||||
className={`relative rounded-full transition-all duration-300 flex-shrink-0 border border-white/20 ${!danmakuApiUrl
|
||||
? 'bg-white/5 opacity-40 cursor-not-allowed'
|
||||
: danmakuEnabled
|
||||
? 'bg-[var(--accent-color)] shadow-[0_0_15px_rgba(var(--accent-color-rgb),0.6)] cursor-pointer'
|
||||
: 'bg-white/5 hover:bg-white/10 cursor-pointer'
|
||||
} ${isRotated ? 'w-6 h-3.5' : 'w-8 h-[18px] sm:w-10 sm:h-6'}`}
|
||||
aria-checked={danmakuEnabled}
|
||||
role="switch"
|
||||
>
|
||||
<span>{danmakuFontSize}px</span>
|
||||
<span
|
||||
className={`absolute top-0.5 left-0.5 bg-white rounded-full transition-transform duration-300 shadow-[0_2px_4px_rgba(0,0,0,0.4)] ${isRotated ? 'w-2.5 h-2.5' : 'w-3.5 h-3.5 sm:w-4.5 sm:h-4.5'} ${danmakuEnabled && danmakuApiUrl ? (isRotated ? 'translate-x-2.5' : 'translate-x-3.5 sm:translate-x-4.5') : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ interface DesktopRightControlsProps {
|
||||
isAirPlaySupported: boolean;
|
||||
isCastAvailable: boolean;
|
||||
isProxied?: boolean;
|
||||
danmakuEnabled?: boolean;
|
||||
onToggleDanmaku?: () => void;
|
||||
onToggleFullscreen: () => void;
|
||||
onTogglePictureInPicture: () => void;
|
||||
onShowAirPlayMenu: () => void;
|
||||
@@ -23,8 +21,6 @@ export function DesktopRightControls({
|
||||
isAirPlaySupported,
|
||||
isCastAvailable,
|
||||
isProxied,
|
||||
danmakuEnabled,
|
||||
onToggleDanmaku,
|
||||
onToggleFullscreen,
|
||||
onTogglePictureInPicture,
|
||||
onShowAirPlayMenu,
|
||||
@@ -32,21 +28,6 @@ export function DesktopRightControls({
|
||||
}: DesktopRightControlsProps) {
|
||||
return (
|
||||
<div className="relative z-50 flex items-center gap-3">
|
||||
{/* Danmaku Toggle */}
|
||||
{onToggleDanmaku && (
|
||||
<button
|
||||
onClick={onToggleDanmaku}
|
||||
className="btn-icon"
|
||||
aria-label={danmakuEnabled ? '关闭弹幕' : '开启弹幕'}
|
||||
title={danmakuEnabled ? '关闭弹幕' : '开启弹幕'}
|
||||
>
|
||||
<Icons.Danmaku
|
||||
size={20}
|
||||
className={danmakuEnabled ? 'text-[var(--accent-color)]' : ''}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Picture-in-Picture */}
|
||||
{
|
||||
isPiPSupported && (
|
||||
|
||||
@@ -13,13 +13,27 @@ interface PlayerSettingsProps {
|
||||
onFullscreenTypeChange: (type: 'auto' | 'native' | 'window') => void;
|
||||
proxyMode: ProxyMode;
|
||||
onProxyModeChange: (mode: ProxyMode) => void;
|
||||
danmakuApiUrl: string;
|
||||
onDanmakuApiUrlChange: (url: string) => void;
|
||||
danmakuOpacity: number;
|
||||
onDanmakuOpacityChange: (value: number) => void;
|
||||
danmakuFontSize: number;
|
||||
onDanmakuFontSizeChange: (value: number) => void;
|
||||
}
|
||||
|
||||
const DANMAKU_FONT_SIZES = [14, 18, 20, 24, 28];
|
||||
|
||||
export function PlayerSettings({
|
||||
fullscreenType,
|
||||
onFullscreenTypeChange,
|
||||
proxyMode,
|
||||
onProxyModeChange,
|
||||
danmakuApiUrl,
|
||||
onDanmakuApiUrlChange,
|
||||
danmakuOpacity,
|
||||
onDanmakuOpacityChange,
|
||||
danmakuFontSize,
|
||||
onDanmakuFontSizeChange,
|
||||
}: PlayerSettingsProps) {
|
||||
return (
|
||||
<div className="bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] shadow-[var(--shadow-sm)] p-6 mb-6">
|
||||
@@ -103,6 +117,74 @@ export function PlayerSettings({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-[var(--glass-border)]" />
|
||||
|
||||
{/* Danmaku Settings */}
|
||||
<div>
|
||||
<h3 className="font-medium text-[var(--text-color)] mb-2 inline-flex items-center gap-2">
|
||||
<Icons.Danmaku size={18} className="text-[var(--accent-color)]" />
|
||||
弹幕设置
|
||||
</h3>
|
||||
<p className="text-sm text-[var(--text-color-secondary)] mb-4">
|
||||
配置弹幕聚合 API 地址,在播放器菜单中开关弹幕
|
||||
</p>
|
||||
|
||||
{/* API URL */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-color)] mb-2">
|
||||
API 地址
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="https://your-danmu-api.example.com"
|
||||
value={danmakuApiUrl}
|
||||
onChange={(e) => onDanmakuApiUrlChange(e.target.value)}
|
||||
className="w-full px-4 py-2.5 bg-[var(--glass-bg)] border border-[var(--glass-border)] rounded-[var(--radius-2xl)] text-[var(--text-color)] placeholder:text-[var(--text-color-secondary)]/50 focus:outline-none focus:border-[var(--accent-color)] transition-colors text-sm"
|
||||
/>
|
||||
<p className="text-xs text-[var(--text-color-secondary)] mt-1.5">
|
||||
兼容 <a href="https://github.com/huangxd-/danmu_api" target="_blank" rel="noopener noreferrer" className="text-[var(--accent-color)] hover:underline">danmu_api</a> 格式的弹幕聚合服务
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Opacity */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-color)] mb-2">
|
||||
弹幕透明度:{Math.round(danmakuOpacity * 100)}%
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
min="10"
|
||||
max="100"
|
||||
value={Math.round(danmakuOpacity * 100)}
|
||||
onChange={(e) => onDanmakuOpacityChange(parseInt(e.target.value) / 100)}
|
||||
className="w-full accent-[var(--accent-color)] h-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Font Size */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-[var(--text-color)] mb-2">
|
||||
弹幕字号
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
{DANMAKU_FONT_SIZES.map((size) => (
|
||||
<button
|
||||
key={size}
|
||||
onClick={() => onDanmakuFontSizeChange(size)}
|
||||
className={`px-3 py-1.5 rounded-[var(--radius-2xl)] border text-sm font-medium transition-all duration-200 cursor-pointer ${danmakuFontSize === size
|
||||
? 'bg-[var(--accent-color)] border-[var(--accent-color)] text-white shadow-[0_4px_12px_rgba(var(--accent-color-rgb),0.3)]'
|
||||
: 'bg-[var(--glass-bg)] border-[var(--glass-border)] text-[var(--text-color)] hover:bg-[color-mix(in_srgb,var(--accent-color)_10%,transparent)]'
|
||||
}`}
|
||||
>
|
||||
{size}px
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -123,7 +123,7 @@ function getDefaultAppSettings(): AppSettings {
|
||||
proxyMode: 'retry',
|
||||
rememberScrollPosition: true,
|
||||
danmakuEnabled: false,
|
||||
danmakuApiUrl: '',
|
||||
danmakuApiUrl: process.env.NEXT_PUBLIC_DANMAKU_API_URL || '',
|
||||
danmakuOpacity: 0.7,
|
||||
danmakuFontSize: 20,
|
||||
};
|
||||
@@ -203,7 +203,7 @@ export const settingsStore = {
|
||||
proxyMode: (parsed.proxyMode === 'retry' || parsed.proxyMode === 'none' || parsed.proxyMode === 'always') ? parsed.proxyMode : 'retry',
|
||||
rememberScrollPosition: parsed.rememberScrollPosition !== undefined ? parsed.rememberScrollPosition : true,
|
||||
danmakuEnabled: parsed.danmakuEnabled !== undefined ? parsed.danmakuEnabled : false,
|
||||
danmakuApiUrl: typeof parsed.danmakuApiUrl === 'string' ? parsed.danmakuApiUrl : '',
|
||||
danmakuApiUrl: typeof parsed.danmakuApiUrl === 'string' ? (parsed.danmakuApiUrl || process.env.NEXT_PUBLIC_DANMAKU_API_URL || '') : (process.env.NEXT_PUBLIC_DANMAKU_API_URL || ''),
|
||||
danmakuOpacity: typeof parsed.danmakuOpacity === 'number' ? parsed.danmakuOpacity : 0.7,
|
||||
danmakuFontSize: typeof parsed.danmakuFontSize === 'number' ? parsed.danmakuFontSize : 20,
|
||||
};
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "kvideo",
|
||||
"version": "4.3.0",
|
||||
"version": "4.3.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kvideo",
|
||||
"version": "4.3.0",
|
||||
"version": "4.3.1",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "kvideo",
|
||||
"version": "4.3.0",
|
||||
"version": "4.3.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
||||
Reference in New Issue
Block a user