Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 41 additions & 45 deletions packages/player-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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]
19 changes: 15 additions & 4 deletions packages/player-core/src/player.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{
fmt::Debug,
fs::File,
io::{Read, Seek},
io::{Cursor, Read, Seek},
sync::{
Arc,
atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering},
Expand Down Expand Up @@ -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<dyn CustomMediaSource> = Box::new(file);
let source_stream: Box<dyn CustomMediaSource> =
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;
Expand Down
9 changes: 7 additions & 2 deletions packages/player/src/components/LocalMusicContext/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
6 changes: 5 additions & 1 deletion packages/player/src/components/NowPlaylistCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 || "未知艺术家";

Expand Down
13 changes: 11 additions & 2 deletions packages/player/src/components/PlaylistCover/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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([]);
Expand Down
10 changes: 8 additions & 2 deletions packages/player/src/utils/use-song-cover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -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);
Expand Down