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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ app.*.map.json
# 添加以下内容
**/android/key.properties
**/android/app/upload-keystore.jks

# mise
mise.local.toml
3 changes: 3 additions & 0 deletions l10n.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
arb-dir: lib/l10n
template-arb-file: app_zh.arb
output-localization-file: app_localizations.dart
19 changes: 19 additions & 0 deletions lib/common/extensions/mark_status_localizations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'package:asmrapp/data/models/mark_status.dart';
import 'package:asmrapp/l10n/app_localizations.dart';

extension MarkStatusLocalizations on MarkStatus {
String localizedLabel(AppLocalizations l10n) {
switch (this) {
case MarkStatus.wantToListen:
return l10n.markStatusWantToListen;
case MarkStatus.listening:
return l10n.markStatusListening;
case MarkStatus.listened:
return l10n.markStatusListened;
case MarkStatus.relistening:
return l10n.markStatusRelistening;
case MarkStatus.onHold:
return l10n.markStatusOnHold;
}
}
}
12 changes: 12 additions & 0 deletions lib/common/utils/playlist_localizations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:asmrapp/l10n/app_localizations.dart';

String localizedPlaylistName(String? name, AppLocalizations l10n) {
switch (name) {
case '__SYS_PLAYLIST_MARKED':
return l10n.playlistSystemMarked;
case '__SYS_PLAYLIST_LIKED':
return l10n.playlistSystemLiked;
default:
return name ?? '';
}
}
2 changes: 1 addition & 1 deletion lib/core/audio/audio_player_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class AudioPlayerHandler extends BaseAudioHandler {

AudioPlayerHandler(this._player, this._eventHub) {
AppLogger.debug('AudioPlayerHandler 初始化');

// 改为监听 EventHub
_eventHub.playbackState.listen((event) {
final state = PlaybackState(
Expand Down
11 changes: 6 additions & 5 deletions lib/core/audio/audio_player_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ class AudioPlayerService implements IAudioPlayerService {
AudioPlayerService._internal({
required PlaybackEventHub eventHub,
required IPlaybackStateRepository stateRepository,
}) : _eventHub = eventHub,
_stateRepository = stateRepository {
}) : _eventHub = eventHub,
_stateRepository = stateRepository {
_init();
}

static AudioPlayerService? _instance;

factory AudioPlayerService({
required PlaybackEventHub eventHub,
required IPlaybackStateRepository stateRepository,
Expand Down Expand Up @@ -130,14 +130,15 @@ class AudioPlayerService implements IAudioPlayerService {
try {
AppLogger.debug('开始恢复播放状态');
final state = await _stateManager.loadState();

if (state == null) {
AppLogger.debug('没有可恢复的播放状态');
return;
}

AppLogger.debug('已加载保存的状态: workId=${state.work.id}');
AppLogger.debug('播放列表信息: 长度=${state.playlist.length}, 索引=${state.currentIndex}');
AppLogger.debug(
'播放列表信息: 长度=${state.playlist.length}, 索引=${state.currentIndex}');

if (state.playlist.isEmpty) {
AppLogger.debug('保存的播放列表为空,跳过恢复');
Expand Down
31 changes: 14 additions & 17 deletions lib/core/audio/cache/audio_cache_manager.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import 'dart:convert';
import 'dart:io';
import 'package:path_provider/path_provider.dart';

import 'package:asmrapp/utils/logger.dart';
import 'package:crypto/crypto.dart';
import 'dart:convert';
import 'package:just_audio/just_audio.dart';
import 'package:asmrapp/utils/logger.dart';
import 'package:path_provider/path_provider.dart';

/// 音频缓存管理器
/// 负责管理音频文件的缓存,对外隐藏具体的缓存实现
Expand All @@ -18,18 +19,17 @@ class AudioCacheManager {
final cacheFile = await _getCacheFile(url);
final fileName = _generateFileName(url);
AppLogger.debug('准备创建音频源 - URL: $url, 缓存文件名: $fileName');

// 检查缓存文件是否存在且有效
final isValid = await _isCacheValid(cacheFile, fileName);

if (isValid) {
AppLogger.debug('[$fileName] 使用已有缓存文件');
return _createCachingSource(url, cacheFile);
}

AppLogger.debug('[$fileName] 创建新的缓存源');
return _createCachingSource(url, cacheFile);

} catch (e) {
AppLogger.error('创建缓存音频源失败,使用非缓存源', e);
return ProgressiveAudioSource(Uri.parse(url));
Expand All @@ -41,7 +41,7 @@ class AudioCacheManager {
try {
final cacheDir = await _getCacheDir();
final files = await cacheDir.list().toList();

// 按修改时间排序
files.sort((a, b) {
return a.statSync().modified.compareTo(b.statSync().modified);
Expand All @@ -51,15 +51,15 @@ class AudioCacheManager {
for (var file in files) {
if (file is File) {
final stat = await file.stat();

// 检查是否过期
if (DateTime.now().difference(stat.modified) > _cacheExpiration) {
await file.delete();
continue;
}

totalSize += stat.size;

// 如果总大小超过限制,删除最旧的文件
if (totalSize > _maxCacheSize) {
await file.delete();
Expand All @@ -76,7 +76,7 @@ class AudioCacheManager {
try {
final cacheDir = await _getCacheDir();
final files = await cacheDir.list().toList();

var totalSize = 0;
for (var file in files) {
if (file is File) {
Expand All @@ -94,10 +94,7 @@ class AudioCacheManager {

/// 创建缓存音频源
static AudioSource _createCachingSource(String url, File cacheFile) {
return LockCachingAudioSource(
Uri.parse(url),
cacheFile: cacheFile
);
return LockCachingAudioSource(Uri.parse(url), cacheFile: cacheFile);
}

/// 检查缓存是否有效
Expand All @@ -112,9 +109,9 @@ class AudioCacheManager {
final stat = await cacheFile.stat();
final size = stat.size;
final age = DateTime.now().difference(stat.modified);

AppLogger.debug('[$fileName] 缓存验证: 大小=${size}bytes, 年龄=$age');

// 移除单个文件大小检查,只保留过期检查
if (age > _cacheExpiration) {
AppLogger.debug('[$fileName] 缓存无效: 文件过期 ($age > $_cacheExpiration)');
Expand Down Expand Up @@ -153,4 +150,4 @@ class AudioCacheManager {
}
return audioCacheDir;
}
}
}
41 changes: 21 additions & 20 deletions lib/core/audio/controllers/playback_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import '../utils/audio_error_handler.dart';
import 'package:asmrapp/data/models/files/child.dart';
import 'package:asmrapp/data/models/works/work.dart';


class PlaybackController {
final AudioPlayer _player;
final PlaybackStateManager _stateManager;
Expand All @@ -17,16 +16,17 @@ class PlaybackController {
required AudioPlayer player,
required PlaybackStateManager stateManager,
required ConcatenatingAudioSource playlist,
}) : _player = player,
_stateManager = stateManager,
_playlist = playlist;
}) : _player = player,
_stateManager = stateManager,
_playlist = playlist;

// 基础播放控制
Future<void> play() => _player.play();
Future<void> pause() => _player.pause();
Future<void> stop() => _player.stop();
Future<void> seek(Duration position, {int? index}) => _player.seek(position, index: index);

Future<void> seek(Duration position, {int? index}) =>
_player.seek(position, index: index);

// 播放列表控制
Future<void> next() async {
try {
Expand Down Expand Up @@ -66,9 +66,7 @@ class PlaybackController {
AppLogger.debug('获取到上一个文件: ${previousFile?.title}');
if (previousFile != null) {
_updateTrackAndContext(
previousFile,
_stateManager.currentContext!.work
);
previousFile, _stateManager.currentContext!.work);
AppLogger.debug('执行切换到上一曲');
await _player.seekToPrevious();
}
Expand All @@ -87,31 +85,34 @@ class PlaybackController {
}

// 播放上下文设置
Future<void> setPlaybackContext(PlaybackContext context, {Duration? initialPosition}) async {
Future<void> setPlaybackContext(PlaybackContext context,
{Duration? initialPosition}) async {
try {
AppLogger.debug('准备设置播放上下文: workId=${context.work.id}, file=${context.currentFile.title}');
AppLogger.debug('播放列表状态: 长度=${context.playlist.length}, 当前索引=${context.currentIndex}');

AppLogger.debug(
'准备设置播放上下文: workId=${context.work.id}, file=${context.currentFile.title}');
AppLogger.debug(
'播放列表状态: 长度=${context.playlist.length}, 当前索引=${context.currentIndex}');

// 验证上下文
try {
context.validate();
} catch (e) {
AppLogger.error('播放上下文验证失败', e);
rethrow;
}

// 1. 先停止当前播放
AppLogger.debug('停止当前播放');
await _player.stop();

// 2. 等待播放器就绪
AppLogger.debug('暂停播放器');
await _player.pause();

// 3. 更新上下文
AppLogger.debug('更新播放上下文');
_stateManager.updateContext(context);

// 4. 设置新的播放源
AppLogger.debug('设置播放源: 初始位置=${initialPosition?.inMilliseconds}ms');
try {
Expand All @@ -131,11 +132,11 @@ class PlaybackController {
// 删掉,会导致播放器索引回到0
// AppLogger.debug('等待播放器加载');
// await _player.load();

// 6. 更新轨道信息
AppLogger.debug('更新轨道信息');
_updateTrackAndContext(context.currentFile, context.work);

AppLogger.debug('播放上下文设置完成');
} catch (e, stack) {
AppLogger.error('设置播放上下文失败', e, stack);
Expand All @@ -154,4 +155,4 @@ class PlaybackController {
AppLogger.debug('更新轨道和上下文: file=${file.title}');
_stateManager.updateTrackAndContext(file, work);
}
}
}
2 changes: 1 addition & 1 deletion lib/core/audio/events/playback_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ class InitialStateEvent extends PlaybackEvent {
final AudioTrackInfo? track;
final PlaybackContext? context;
InitialStateEvent(this.track, this.context);
}
}
37 changes: 18 additions & 19 deletions lib/core/audio/events/playback_event_hub.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,32 @@ class PlaybackEventHub {
final _eventSubject = PublishSubject<PlaybackEvent>();

// 分类后的特定事件流
late final Stream<PlaybackStateEvent> playbackState = _eventSubject
.whereType<PlaybackStateEvent>()
.distinct();

late final Stream<TrackChangeEvent> trackChange = _eventSubject
.whereType<TrackChangeEvent>();

late final Stream<PlaybackContextEvent> contextChange = _eventSubject
.whereType<PlaybackContextEvent>();

late final Stream<PlaybackStateEvent> playbackState =
_eventSubject.whereType<PlaybackStateEvent>().distinct();

late final Stream<TrackChangeEvent> trackChange =
_eventSubject.whereType<TrackChangeEvent>();

late final Stream<PlaybackContextEvent> contextChange =
_eventSubject.whereType<PlaybackContextEvent>();

late final Stream<PlaybackProgressEvent> playbackProgress = _eventSubject
.whereType<PlaybackProgressEvent>()
.distinct((prev, next) => prev.position == next.position);
late final Stream<PlaybackErrorEvent> errors = _eventSubject
.whereType<PlaybackErrorEvent>();

late final Stream<PlaybackErrorEvent> errors =
_eventSubject.whereType<PlaybackErrorEvent>();

// 添加新的事件流
late final Stream<InitialStateEvent> initialState = _eventSubject
.whereType<InitialStateEvent>();
late final Stream<RequestInitialStateEvent> requestInitialState = _eventSubject
.whereType<RequestInitialStateEvent>();
late final Stream<InitialStateEvent> initialState =
_eventSubject.whereType<InitialStateEvent>();

late final Stream<RequestInitialStateEvent> requestInitialState =
_eventSubject.whereType<RequestInitialStateEvent>();

// 发送事件
void emit(PlaybackEvent event) => _eventSubject.add(event);

// 资源释放
void dispose() => _eventSubject.close();
}
}
2 changes: 1 addition & 1 deletion lib/core/audio/i_audio_player_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ abstract class IAudioPlayerService {

// 上下文管理
Future<void> playWithContext(PlaybackContext context);

// 状态访问
AudioTrackInfo? get currentTrack;
PlaybackContext? get currentContext;
Expand Down
Loading