音乐增加代理开关

This commit is contained in:
mtvpls
2026-04-14 16:49:22 +08:00
parent 1dcf27d1f8
commit 0e41ba2661
6 changed files with 178 additions and 90 deletions
+23
View File
@@ -11537,12 +11537,14 @@ const MusicConfigComponent = ({
const [enabled, setEnabled] = useState(false);
const [baseUrl, setBaseUrl] = useState('');
const [token, setToken] = useState('');
const [proxyEnabled, setProxyEnabled] = useState(true);
useEffect(() => {
if (config?.MusicConfig) {
setEnabled(config.MusicConfig.Enabled || false);
setBaseUrl(config.MusicConfig.BaseUrl || '');
setToken(config.MusicConfig.Token || '');
setProxyEnabled(config.MusicConfig.ProxyEnabled ?? true);
}
}, [config]);
@@ -11562,6 +11564,7 @@ const MusicConfigComponent = ({
Enabled: enabled,
BaseUrl: normalizedBaseUrl,
Token: token.trim(),
ProxyEnabled: proxyEnabled,
}),
});
@@ -11628,6 +11631,26 @@ const MusicConfigComponent = ({
</div>
<div className='space-y-4'>
<div className='flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700'>
<div>
<h3 className='text-sm font-medium text-gray-900 dark:text-gray-100'>
</h3>
<p className='text-xs text-gray-500 dark:text-gray-400 mt-1'>
</p>
</div>
<label className='relative inline-flex items-center cursor-pointer'>
<input
type='checkbox'
checked={proxyEnabled}
onChange={(e) => setProxyEnabled(e.target.checked)}
className='sr-only peer'
/>
<div className="w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-green-300 dark:peer-focus:ring-green-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:start-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-green-600"></div>
</label>
</div>
<div>
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
lxserver Base URL
+5 -1
View File
@@ -32,17 +32,20 @@ export async function POST(request: NextRequest) {
Enabled,
BaseUrl,
Token,
ProxyEnabled,
} = body as {
Enabled?: boolean;
BaseUrl?: string;
Token?: string;
ProxyEnabled?: boolean;
};
// 参数校验
if (
(Enabled !== undefined && typeof Enabled !== 'boolean') ||
(BaseUrl !== undefined && typeof BaseUrl !== 'string') ||
(Token !== undefined && typeof Token !== 'string')
(Token !== undefined && typeof Token !== 'string') ||
(ProxyEnabled !== undefined && typeof ProxyEnabled !== 'boolean')
) {
return NextResponse.json({ error: '参数格式错误' }, { status: 400 });
}
@@ -62,6 +65,7 @@ export async function POST(request: NextRequest) {
Enabled,
BaseUrl,
Token,
ProxyEnabled: ProxyEnabled ?? true,
};
// 写入数据库
+3
View File
@@ -93,6 +93,7 @@ export default async function RootLayout({
let webLiveEnabled = false;
let customAdFilterVersion = 0;
let tuneHubEnabled = false;
let musicProxyEnabled = true;
let advancedRecommendationEnabled = false;
let customCategories = [] as {
name: string;
@@ -149,6 +150,7 @@ export default async function RootLayout({
customAdFilterVersion = config.SiteConfig?.CustomAdFilterVersion || 0;
// 音乐功能配置
tuneHubEnabled = config.MusicConfig?.Enabled || false;
musicProxyEnabled = config.MusicConfig?.ProxyEnabled ?? true;
// 高级推荐功能配置:存在已启用视频源脚本时显示
advancedRecommendationEnabled =
(await listEnabledSourceScripts()).length > 0;
@@ -223,6 +225,7 @@ export default async function RootLayout({
ADVANCED_RECOMMENDATION_ENABLED: advancedRecommendationEnabled,
CUSTOM_AD_FILTER_VERSION: customAdFilterVersion,
MUSIC_ENABLED: tuneHubEnabled,
MUSIC_PROXY_ENABLED: musicProxyEnabled,
FESTIVE_EFFECT_ENABLED:
process.env.FESTIVE_EFFECT_ENABLED === 'true',
};
+143 -89
View File
@@ -88,6 +88,10 @@ export default function MusicPage() {
const [showPlayer, setShowPlayer] = useState(false);
const [loading, setLoading] = useState(false);
const [showLyrics, setShowLyrics] = useState(false);
const [musicProxyEnabled, setMusicProxyEnabled] = useState(() => {
if (typeof window === 'undefined') return true;
return (window as any).RUNTIME_CONFIG?.MUSIC_PROXY_ENABLED !== false;
});
const [lyrics, setLyrics] = useState<LyricLine[]>([]);
const [currentLyricIndex, setCurrentLyricIndex] = useState(-1);
const [currentSongUrl, setCurrentSongUrl] = useState('');
@@ -190,6 +194,40 @@ export default function MusicPage() {
return `/api/music/v2/stream?${params.toString()}`;
};
const getMusicProxyEnabled = () => {
if (typeof window === 'undefined') return true;
return (window as any).RUNTIME_CONFIG?.MUSIC_PROXY_ENABLED !== false;
};
const fetchPlayData = async (
song: Song,
source: MusicSource,
songQuality: '128k' | '320k' | 'flac' | 'flac24bit',
includeUrl = false
) => {
const response = await fetch('/api/music/v2/play', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
includeUrl,
song: {
songId: song.id,
source,
songmid: song.songmid,
name: song.name,
artist: song.artist,
album: song.album,
cover: song.pic,
durationSec: song.duration,
durationText: song.durationText,
},
quality: songQuality,
}),
});
return response.json();
};
const beginResolving = () => {
setResolvingCount((prev) => prev + 1);
};
@@ -251,6 +289,10 @@ export default function MusicPage() {
// 此函数已不再使用,所有状态恢复都在 initializePlayState 中完成
};
useEffect(() => {
setMusicProxyEnabled(getMusicProxyEnabled());
}, []);
// 页面加载时恢复播放状态和数据库记录
useEffect(() => {
const initializePlayState = async () => {
@@ -300,6 +342,8 @@ export default function MusicPage() {
// 4. 使用数据库的最新记录(歌曲和进度都从数据库获取)
if (sortedRecords.length > 0) {
const proxyEnabled = getMusicProxyEnabled();
setMusicProxyEnabled(proxyEnabled);
const latestDbRecord = sortedRecords[0];
const latestDbSong = sortedSongs[0];
@@ -312,53 +356,49 @@ export default function MusicPage() {
const dbPlayTime = latestDbRecord.playTime || 0;
songStartTimeRef.current = Date.now();
// 5. 先直接使用稳定 stream 地址恢复播放
const platform = latestDbSong.platform || 'kw';
const streamUrl = buildStreamUrl(latestDbSong, platform, playState.quality || '320k');
setCurrentSongUrl(streamUrl);
const selectedQuality = playState.quality || '320k';
if (audioRef.current) {
audioRef.current.src = streamUrl;
const restoreTime = () => {
if (audioRef.current && dbPlayTime > 0) {
audioRef.current.currentTime = dbPlayTime;
}
};
const restoreTime = () => {
if (audioRef.current && dbPlayTime > 0) {
audioRef.current.currentTime = dbPlayTime;
}
};
if (proxyEnabled) {
const streamUrl = buildStreamUrl(latestDbSong, platform, selectedQuality);
setCurrentSongUrl(streamUrl);
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
audioRef.current.load();
}
if (audioRef.current) {
audioRef.current.src = streamUrl;
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
audioRef.current.load();
}
fetch('/api/music/v2/play', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
includeUrl: false,
song: {
songId: latestDbSong.id,
source: platform,
songmid: latestDbSong.songmid,
name: latestDbSong.name,
artist: latestDbSong.artist,
album: latestDbSong.album,
cover: latestDbSong.pic,
durationSec: latestDbSong.duration,
durationText: latestDbSong.durationText,
},
quality: playState.quality || '320k',
}),
})
.then(res => res.json())
.then((data) => {
if (data.success && data.data?.lyric?.lyric) {
fetchPlayData(latestDbSong, platform, selectedQuality, false)
.then((data) => {
if (data.success && data.data?.lyric?.lyric) {
const parsedLyrics = parseLyric(data.data.lyric.lyric, data.data.lyric.tlyric);
setLyrics(parsedLyrics);
}
})
.catch((error) => {
console.error('加载歌词失败:', error);
});
} else {
const data = await fetchPlayData(latestDbSong, platform, selectedQuality, true);
if (data.success && data.data?.play?.directUrl && audioRef.current) {
setCurrentSongUrl(data.data.play.directUrl);
audioRef.current.src = data.data.play.directUrl;
audioRef.current.addEventListener('loadedmetadata', restoreTime, { once: true });
audioRef.current.load();
if (data.data.lyric?.lyric) {
const parsedLyrics = parseLyric(data.data.lyric.lyric, data.data.lyric.tlyric);
setLyrics(parsedLyrics);
}
})
.catch((error) => {
console.error('加载歌词失败:', error);
});
}
}
}
} catch (error) {
console.error('加载播放记录失败:', error);
@@ -767,7 +807,9 @@ export default function MusicPage() {
beginResolving();
try {
// 使用歌曲自己的平台信息,如果没有则使用当前选择的平台
const platform = song.platform || currentSource;
const platform = song.platform || currentSource;
const proxyEnabled = getMusicProxyEnabled();
setMusicProxyEnabled(proxyEnabled);
// 记录歌曲开始播放的时间
songStartTimeRef.current = Date.now();
@@ -815,59 +857,71 @@ export default function MusicPage() {
}
});
const streamUrl = buildStreamUrl(song, platform, quality);
setCurrentSongUrl(streamUrl);
if (proxyEnabled) {
const streamUrl = buildStreamUrl(song, platform, quality);
setCurrentSongUrl(streamUrl);
if (audioRef.current) {
audioRef.current.src = streamUrl;
audioRef.current.load();
audioRef.current.play().catch(err => {
console.error('播放失败:', err);
});
setIsPlaying(true);
}
if (audioRef.current) {
audioRef.current.src = streamUrl;
audioRef.current.load();
audioRef.current.play().catch(err => {
console.error('播放失败:', err);
});
setIsPlaying(true);
}
fetch('/api/music/v2/play', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
includeUrl: false,
song: {
songId: song.id,
source: platform,
songmid: song.songmid,
name: song.name,
artist: song.artist,
album: song.album,
cover: song.pic,
durationSec: song.duration,
durationText: song.durationText,
},
quality,
}),
})
.then(res => res.json())
.then((data) => {
if (data.success) {
if (data.data.song?.cover) {
setCurrentSong({
...song,
pic: data.data.song.cover,
platform,
});
fetchPlayData(song, platform, quality, false)
.then((data) => {
if (data.success) {
if (data.data.song?.cover) {
setCurrentSong({
...song,
pic: data.data.song.cover,
platform,
});
}
if (data.data.lyric?.lyric) {
const parsedLyrics = parseLyric(data.data.lyric.lyric, data.data.lyric.tlyric);
setLyrics(parsedLyrics);
}
} else {
console.error('播放信息获取失败:', data);
}
if (data.data.lyric?.lyric) {
const parsedLyrics = parseLyric(data.data.lyric.lyric, data.data.lyric.tlyric);
setLyrics(parsedLyrics);
}
} else {
console.error('播放信息获取失败:', data);
})
.catch((error) => {
console.error('加载歌词失败:', error);
});
} else {
const data = await fetchPlayData(song, platform, quality, true);
if (data.success && data.data?.play?.directUrl) {
if (data.data.song?.cover) {
setCurrentSong({
...song,
pic: data.data.song.cover,
platform,
});
}
})
.catch((error) => {
console.error('加载歌词失败:', error);
});
if (data.data.lyric?.lyric) {
const parsedLyrics = parseLyric(data.data.lyric.lyric, data.data.lyric.tlyric);
setLyrics(parsedLyrics);
}
setCurrentSongUrl(data.data.play.directUrl);
if (audioRef.current) {
audioRef.current.src = data.data.play.directUrl;
audioRef.current.load();
audioRef.current.play().catch(err => {
console.error('播放失败:', err);
});
setIsPlaying(true);
}
} else {
console.error('播放信息获取失败:', data);
}
}
} catch (error) {
console.error('播放失败:', error);
} finally {
+1
View File
@@ -264,6 +264,7 @@ export interface AdminConfig {
Enabled?: boolean; // 启用音乐功能
BaseUrl?: string; // lxserver 地址
Token?: string; // lxserver x-user-token
ProxyEnabled?: boolean; // 是否走 stream 代理
// 兼容旧代码的遗留字段(待删除)
TuneHubEnabled?: boolean;
TuneHubBaseUrl?: string;
+3
View File
@@ -646,7 +646,10 @@ export function configSelfCheck(adminConfig: AdminConfig): AdminConfig {
Enabled: false,
BaseUrl: '',
Token: '',
ProxyEnabled: true,
};
} else if (adminConfig.MusicConfig.ProxyEnabled === undefined) {
adminConfig.MusicConfig.ProxyEnabled = true;
}
return adminConfig;