音乐增加代理开关
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
// 写入数据库
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user