fix(playback): 播放页/迷你播放器立即显示已有封面,不再等元数据流程
原因有两层: 1) Song 模型没有封面路径,播放时只带 url/title/artist, 播放页只能等元数据服务返回 artwork 字节后才显示; 2) 元数据回来后用新 Song 覆盖当前歌曲时丢了 artworkPath, 即使带上了也会被冲掉。 - Song 增加 artworkPath 字段 - PlaybackController / 首页收藏 / playPlaylist 构造 Song 时带上 - 播放页:artwork 字节优先,其次直接读 artwork_path 文件 - 迷你播放器同样加 FileImage 兜底 - 元数据更新重建 Song 时保留 artworkPath 真机验证:点击播放后封面立即出现,不再先显示音符占位。
This commit is contained in:
@@ -98,6 +98,7 @@ class PlaybackController {
|
|||||||
final dbSong = await _db.getSongByPath(songPath);
|
final dbSong = await _db.getSongByPath(songPath);
|
||||||
final song = Song(
|
final song = Song(
|
||||||
id: songPath,
|
id: songPath,
|
||||||
|
artworkPath: dbSong?['artwork_path'] as String?,
|
||||||
title: dbSong?['title'] as String? ??
|
title: dbSong?['title'] as String? ??
|
||||||
songPath.split('/').last.replaceAll(RegExp(r'\.[^.]*$'), ''),
|
songPath.split('/').last.replaceAll(RegExp(r'\.[^.]*$'), ''),
|
||||||
artist: dbSong?['artist'] as String? ?? '未知艺术家',
|
artist: dbSong?['artist'] as String? ?? '未知艺术家',
|
||||||
@@ -117,6 +118,7 @@ class PlaybackController {
|
|||||||
if (dbSong != null) {
|
if (dbSong != null) {
|
||||||
queue.add(Song(
|
queue.add(Song(
|
||||||
id: item.path,
|
id: item.path,
|
||||||
|
artworkPath: dbSong['artwork_path'] as String?,
|
||||||
title: dbSong['title'] as String? ??
|
title: dbSong['title'] as String? ??
|
||||||
item.name.replaceAll(RegExp(r'\.[^.]*$'), ''),
|
item.name.replaceAll(RegExp(r'\.[^.]*$'), ''),
|
||||||
artist: dbSong['artist'] as String? ?? '未知艺术家',
|
artist: dbSong['artist'] as String? ?? '未知艺术家',
|
||||||
|
|||||||
@@ -378,6 +378,7 @@ class _HomePageState extends BaseState<HomePage> {
|
|||||||
id: song.path,
|
id: song.path,
|
||||||
title: song.displayTitle,
|
title: song.displayTitle,
|
||||||
artist: song.displaySubtitle,
|
artist: song.displaySubtitle,
|
||||||
|
artworkPath: song.artworkPath,
|
||||||
url: url,
|
url: url,
|
||||||
));
|
));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -619,6 +620,7 @@ class _HomePageState extends BaseState<HomePage> {
|
|||||||
id: item.path,
|
id: item.path,
|
||||||
title: item.displayTitle,
|
title: item.displayTitle,
|
||||||
artist: item.displaySubtitle,
|
artist: item.displaySubtitle,
|
||||||
|
artworkPath: item.artworkPath,
|
||||||
url: WebDAVService.instance.getFileUrl(item.path),
|
url: WebDAVService.instance.getFileUrl(item.path),
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:io';
|
||||||
// lib/pages/player_page.dart
|
// lib/pages/player_page.dart
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -322,6 +323,11 @@ class _PlayerPageState extends BaseState<PlayerPage> {
|
|||||||
? (screen.height * 0.38).clamp(160.0, 380.0)
|
? (screen.height * 0.38).clamp(160.0, 380.0)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
final artworkPath = song.artworkPath;
|
||||||
|
final hasArtworkFile = artworkPath != null &&
|
||||||
|
artworkPath.isNotEmpty &&
|
||||||
|
File(artworkPath).existsSync();
|
||||||
|
|
||||||
final cover = ClipRRect(
|
final cover = ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(compact ? 18 : 24),
|
borderRadius: BorderRadius.circular(compact ? 18 : 24),
|
||||||
child: song.artwork != null
|
child: song.artwork != null
|
||||||
@@ -330,14 +336,20 @@ class _PlayerPageState extends BaseState<PlayerPage> {
|
|||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
gaplessPlayback: true,
|
gaplessPlayback: true,
|
||||||
)
|
)
|
||||||
: Container(
|
: hasArtworkFile
|
||||||
color: const Color(0xFF2A3332),
|
? Image.file(
|
||||||
child: Icon(
|
File(artworkPath),
|
||||||
Icons.music_note,
|
fit: BoxFit.cover,
|
||||||
size: compact ? 48 : 72,
|
gaplessPlayback: true,
|
||||||
color: Colors.white24,
|
)
|
||||||
),
|
: Container(
|
||||||
),
|
color: const Color(0xFF2A3332),
|
||||||
|
child: Icon(
|
||||||
|
Icons.music_note,
|
||||||
|
size: compact ? 48 : 72,
|
||||||
|
color: Colors.white24,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final padded = Padding(
|
final padded = Padding(
|
||||||
|
|||||||
@@ -32,12 +32,17 @@ class Song {
|
|||||||
final String? url;
|
final String? url;
|
||||||
final Uint8List? artwork;
|
final Uint8List? artwork;
|
||||||
|
|
||||||
|
/// 本地封面文件路径(songs.artwork_path)。
|
||||||
|
/// 命中过元数据的歌直接用它显示封面,不必等元数据流程回来。
|
||||||
|
final String? artworkPath;
|
||||||
|
|
||||||
Song({
|
Song({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.artist,
|
required this.artist,
|
||||||
this.url,
|
this.url,
|
||||||
this.artwork,
|
this.artwork,
|
||||||
|
this.artworkPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
@@ -299,6 +304,7 @@ class AudioService extends ChangeNotifier {
|
|||||||
final path = dbSong['remote_path'] as String;
|
final path = dbSong['remote_path'] as String;
|
||||||
songs.add(Song(
|
songs.add(Song(
|
||||||
id: path,
|
id: path,
|
||||||
|
artworkPath: dbSong['artwork_path'] as String?,
|
||||||
title: dbSong['title'] as String? ?? '',
|
title: dbSong['title'] as String? ?? '',
|
||||||
artist: dbSong['artist'] as String? ?? '未知艺术家',
|
artist: dbSong['artist'] as String? ?? '未知艺术家',
|
||||||
url: WebDAVService.instance.getFileUrl(path),
|
url: WebDAVService.instance.getFileUrl(path),
|
||||||
@@ -855,6 +861,8 @@ class AudioService extends ChangeNotifier {
|
|||||||
artist: metadata.artist.isNotEmpty ? metadata.artist : song.artist,
|
artist: metadata.artist.isNotEmpty ? metadata.artist : song.artist,
|
||||||
url: song.url,
|
url: song.url,
|
||||||
artwork: metadata.artwork,
|
artwork: metadata.artwork,
|
||||||
|
// 保留封面文件路径:元数据流程回来前,播放页靠它立即显示封面
|
||||||
|
artworkPath: song.artworkPath,
|
||||||
);
|
);
|
||||||
_queue[_currentIndex] = updatedSong;
|
_queue[_currentIndex] = updatedSong;
|
||||||
_currentSong = updatedSong;
|
_currentSong = updatedSong;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:io';
|
||||||
// lib/widgets/global_mini_player.dart
|
// lib/widgets/global_mini_player.dart
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -54,7 +55,6 @@ class GlobalMiniPlayer extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 12),
|
|
||||||
Container(
|
Container(
|
||||||
width: 44,
|
width: 44,
|
||||||
height: 44,
|
height: 44,
|
||||||
@@ -66,9 +66,22 @@ class GlobalMiniPlayer extends StatelessWidget {
|
|||||||
image: MemoryImage(song.artwork!),
|
image: MemoryImage(song.artwork!),
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
)
|
)
|
||||||
: null,
|
: (song.artworkPath != null &&
|
||||||
|
song.artworkPath!.isNotEmpty &&
|
||||||
|
File(song.artworkPath!)
|
||||||
|
.existsSync())
|
||||||
|
? DecorationImage(
|
||||||
|
image: FileImage(
|
||||||
|
File(song.artworkPath!)),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
child: song.artwork == null
|
child: (song.artwork == null &&
|
||||||
|
(song.artworkPath == null ||
|
||||||
|
song.artworkPath!.isEmpty ||
|
||||||
|
!File(song.artworkPath!)
|
||||||
|
.existsSync()))
|
||||||
? const Icon(
|
? const Icon(
|
||||||
Icons.music_note,
|
Icons.music_note,
|
||||||
color: Colors.white38,
|
color: Colors.white38,
|
||||||
|
|||||||
Reference in New Issue
Block a user