diff --git a/Cargo.lock b/Cargo.lock index ec40efdb..d33272c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,6 +161,7 @@ dependencies = [ "now-playing-controls", "parking_lot", "rb", + "reqwest 0.13.4", "ringbuf", "segmap", "serde", diff --git a/packages/player-core/Cargo.toml b/packages/player-core/Cargo.toml index 0a72808a..ed2bf422 100644 --- a/packages/player-core/Cargo.toml +++ b/packages/player-core/Cargo.toml @@ -1,45 +1,41 @@ -[package] -name = "amll-player-core" -version = "0.1.0" -edition = "2024" - -[features] -default = [] - -[dependencies] -anyhow = "^1.0" -cpal = "^0.17" -crossbeam-channel = "0.5.15" -crossbeam-utils = "0.8.21" -futures = "0.3" -md5 = "^0.8" -rb = "^0.4" -ringbuf = "^0.5" -arrayvec = "^0.7" -segmap = "0.1" -serde = { version = "^1.0", features = ["derive"] } -serde_json = "^1.0" -spectrum-analyzer = "1.5" -tokio = { version = "^1", features = [ - "time", - "macros", - "sync", - "rt", - "rt-multi-thread", -] } -tracing = "0.1" -parking_lot = "0.12" - -ffmpeg_audio = { version = "0.1" } -now-playing-controls = { git = "https://github.com/apoint123/now-playing-controls.git" } -tokio-util = "0.7.18" - -[dev-dependencies] -tracing-subscriber = "0.3" -windows = { version = "0.62", features = [ - "Win32_Foundation", - "Win32_UI_WindowsAndMessaging", - "Win32_Graphics_Gdi", -] } - -[build-dependencies] +[package] +name = "amll-player-core" +version = "0.1.0" +edition = "2024" +[features] +default = [] +[dependencies] +anyhow = "^1.0" +cpal = "^0.17" +crossbeam-channel = "0.5.15" +crossbeam-utils = "0.8.21" +futures = "0.3" +md5 = "^0.8" +rb = "^0.4" +ringbuf = "^0.5" +arrayvec = "^0.7" +segmap = "0.1" +serde = { version = "^1.0", features = ["derive"] } +serde_json = "^1.0" +spectrum-analyzer = "1.5" +tokio = { version = "^1", features = [ + "time", + "macros", + "sync", + "rt", + "rt-multi-thread", +] } +tracing = "0.1" +parking_lot = "0.12" +ffmpeg_audio = { version = "0.1" } +now-playing-controls = { git = "https://github.com/apoint123/now-playing-controls.git" } +tokio-util = "0.7.18" +reqwest = { version = "0.13", default-features = false, features = ["rustls", "stream"] } +[dev-dependencies] +tracing-subscriber = "0.3" +windows = { version = "0.62", features = [ + "Win32_Foundation", + "Win32_UI_WindowsAndMessaging", + "Win32_Graphics_Gdi", +] } +[build-dependencies] diff --git a/packages/player-core/src/player.rs b/packages/player-core/src/player.rs index 8c01642f..c0f36e6c 100644 --- a/packages/player-core/src/player.rs +++ b/packages/player-core/src/player.rs @@ -1,7 +1,7 @@ use std::{ fmt::Debug, fs::File, - io::{Read, Seek}, + io::{Cursor, Read, Seek}, sync::{ Arc, atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}, @@ -454,9 +454,20 @@ impl AudioPlayer { let song_data = self.current_song.clone().context("没有当前歌曲可播放")?; - let file = File::open(&song_data.file_path) - .with_context(|| format!("打开 {} 失败", song_data.file_path))?; - let source_stream: Box = Box::new(file); + let source_stream: Box = + if song_data.file_path.starts_with("http://") || song_data.file_path.starts_with("https://") { + let bytes = reqwest::get(&song_data.file_path) + .await + .with_context(|| format!("下载 {} 失败", song_data.file_path))? + .bytes() + .await + .with_context(|| format!("读取 {} 响应失败", song_data.file_path))?; + Box::new(Cursor::new(bytes.to_vec())) + } else { + let file = File::open(&song_data.file_path) + .with_context(|| format!("打开 {} 失败", song_data.file_path))?; + Box::new(file) + }; let target_channels = self.target_channels; let target_sample_rate = self.target_sample_rate; diff --git a/packages/player/src/components/LocalMusicContext/index.tsx b/packages/player/src/components/LocalMusicContext/index.tsx index 7cde5290..5b48795c 100644 --- a/packages/player/src/components/LocalMusicContext/index.tsx +++ b/packages/player/src/components/LocalMusicContext/index.tsx @@ -291,10 +291,15 @@ export const LocalMusicContext: FC = () => { ); if (songFromDb.coverPath) { - store.set(musicCoverAtom, convertFileSrc(songFromDb.coverPath)); + const coverPath = songFromDb.coverPath; + if (coverPath.startsWith("http://") || coverPath.startsWith("https://")) { + store.set(musicCoverAtom, coverPath); + } else { + store.set(musicCoverAtom, convertFileSrc(coverPath)); + } store.set( musicCoverIsVideoAtom, - songFromDb.coverPath.endsWith(".mp4"), + coverPath.endsWith(".mp4"), ); } else { store.set( diff --git a/packages/player/src/components/NowPlaylistCard/index.tsx b/packages/player/src/components/NowPlaylistCard/index.tsx index 2cd8e1c1..34bd4ff2 100644 --- a/packages/player/src/components/NowPlaylistCard/index.tsx +++ b/packages/player/src/components/NowPlaylistCard/index.tsx @@ -22,7 +22,11 @@ const PlaylistSongItem: FC< const playlistIndex = useAtomValue(queueCurrentIndexAtom); const queueManager = useAtomValue(queueManagerAtom); - const cover = song.coverPath ? convertFileSrc(song.coverPath) : ""; + const cover = song.coverPath + ? (song.coverPath.startsWith("http://") || song.coverPath.startsWith("https://") + ? song.coverPath + : convertFileSrc(song.coverPath)) + : ""; const name = song.songName || "未知歌曲"; const artists = song.songArtists || "未知艺术家"; diff --git a/packages/player/src/components/PlaylistCover/index.tsx b/packages/player/src/components/PlaylistCover/index.tsx index 9ed9e9ff..21498888 100644 --- a/packages/player/src/components/PlaylistCover/index.tsx +++ b/packages/player/src/components/PlaylistCover/index.tsx @@ -35,14 +35,23 @@ export const PlaylistCover: FC< useEffect(() => { if (playlist?.coverPath) { - setPlaylistImgs([convertFileSrc(playlist.coverPath)]); + setPlaylistImgs([ + playlist.coverPath.startsWith("http://") || playlist.coverPath.startsWith("https://") + ? playlist.coverPath + : convertFileSrc(playlist.coverPath) + ]); return; } if (songs && songs.length > 0) { const imgs = songs .slice(0, 4) // biome-ignore lint/style/noNonNullAssertion: filter() 检查了 coverPath 的存在 - .map((s) => convertFileSrc(s.coverPath!)); + .map((s) => { + const path = s.coverPath!; + return path.startsWith("http://") || path.startsWith("https://") + ? path + : convertFileSrc(path); + }); setPlaylistImgs(imgs); } else { setPlaylistImgs([]); diff --git a/packages/player/src/utils/use-song-cover.ts b/packages/player/src/utils/use-song-cover.ts index df3bf7e0..a1bc465e 100644 --- a/packages/player/src/utils/use-song-cover.ts +++ b/packages/player/src/utils/use-song-cover.ts @@ -27,7 +27,11 @@ export const useSongCover = (song?: Song) => { // 3. 从 dexie 迁移导入时,使用前端的 mime type // 所以可以确保封面路径总是 jpg 或 mp4 文件,方便判断封面是图片或者视频 if (!song.coverPath.endsWith(".mp4")) { - setSongImgUrl(convertFileSrc(song.coverPath)); + if (song.coverPath.startsWith("http://") || song.coverPath.startsWith("https://")) { + setSongImgUrl(song.coverPath); + } else { + setSongImgUrl(convertFileSrc(song.coverPath)); + } return; } @@ -39,7 +43,9 @@ export const useSongCover = (song?: Song) => { setSongImgUrl(""); const coverPath = song.coverPath; - const videoSrc = convertFileSrc(coverPath); + const videoSrc = coverPath.startsWith("http://") || coverPath.startsWith("https://") + ? coverPath + : convertFileSrc(coverPath); getVideoThumbnail(videoSrc) .then((blob) => { const url = URL.createObjectURL(blob);