diff --git a/lib/features/player/core/abs_audio_player.dart b/lib/features/player/core/abs_audio_player.dart index a82017a..38b6ba6 100644 --- a/lib/features/player/core/abs_audio_player.dart +++ b/lib/features/player/core/abs_audio_player.dart @@ -38,6 +38,8 @@ abstract class AbsAudioPlayer { required String token, Duration? initialPosition, List? downloadedUris, + Duration? start, + Duration? end, }) async { if (_bookStreamController.nvalue == book) { _logger.info('Book is the same, doing nothing'); @@ -69,17 +71,27 @@ abstract class AbsAudioPlayer { _mediaItemController.sink.add(item); final playlist = book.tracks .map( - (track) => - _getUri(track, downloadedUris, baseUrl: baseUrl, token: token), + (track) => ( + _getUri(track, downloadedUris, baseUrl: baseUrl, token: token), + track.duration + ), ) .toList(); - await setPlayList(playlist, index: indexTrack, position: positionInTrack); + await setPlayList( + playlist, + index: indexTrack, + position: positionInTrack, + start: start, + end: end, + ); } Future setPlayList( - List playlist, { + List<(Uri, Duration)> playlist, { int? index, Duration? position, + Duration? start, + Duration? end, }); Future play(); Future pause(); @@ -151,18 +163,20 @@ abstract class AbsAudioPlayer { Duration get position; Stream get positionStream; - Duration get positionInChapter { - final globalPosition = positionInBook; + Duration get positionInChapter => getPositionInChapter(position); + Duration getPositionInChapter(position) { + final globalPosition = getPositionInBook(position); return globalPosition - (book?.findChapterAtTime(globalPosition).start ?? Duration.zero); } - Duration get positionInBook => + Duration get positionInBook => getPositionInBook(position); + Duration getPositionInBook(position) => position + (book?.tracks[currentIndex].startOffset ?? Duration.zero); Stream get positionInChapterStream => positionStream.map((position) { - return positionInChapter; + return getPositionInChapter(position); }); Stream get positionInBookStream => positionStream.map((position) { @@ -336,3 +350,17 @@ extension BookExpandedExtension on BookExpanded { return tracks[index].startOffset; } } + +class AudioMetadata { + final String album; + final String title; + final String artist; + final String artwork; + + AudioMetadata({ + required this.album, + required this.title, + required this.artist, + required this.artwork, + }); +} diff --git a/lib/features/player/core/abs_audio_player_mpv.dart b/lib/features/player/core/abs_audio_player_mpv.dart index 2ea7ea9..48d1e64 100644 --- a/lib/features/player/core/abs_audio_player_mpv.dart +++ b/lib/features/player/core/abs_audio_player_mpv.dart @@ -72,13 +72,15 @@ class AbsMpvAudioPlayer extends AbsAudioPlayer { @override Future setPlayList( - List playlist, { + List<(Uri, Duration)> playlist, { int? index, Duration? position, + Duration? start, + Duration? end, }) async { await player.open( Playlist( - playlist.map((uri) => Media(uri.toString())).toList(), + playlist.map((uri) => Media(uri.$1.toString())).toList(), index: index ?? 0, ), play: false, diff --git a/lib/features/player/core/abs_audio_player_platform.dart b/lib/features/player/core/abs_audio_player_platform.dart index 9508c89..e928e69 100644 --- a/lib/features/player/core/abs_audio_player_platform.dart +++ b/lib/features/player/core/abs_audio_player_platform.dart @@ -9,8 +9,7 @@ final _logger = Logger('AbsPlatformAudioPlayer'); /// 音频播放器 平台ios,macos,android (just_audio) class AbsPlatformAudioPlayer extends AbsAudioPlayer { late final AudioPlayer _player; - AbsPlatformAudioPlayer(AudioPlayer player) { - _player = player; + AbsPlatformAudioPlayer() { // 跳转到播放列表指定条目指定位置 // prefetch-playlist=yes JustAudioMediaKit.prefetchPlaylist = true; @@ -19,8 +18,8 @@ class AbsPlatformAudioPlayer extends AbsAudioPlayer { // cache-pause-wait=60 JustAudioMediaKit.ensureInitialized(); - player = AudioPlayer(); - player.playerStateStream.listen((state) { + _player = AudioPlayer(); + _player.playerStateStream.listen((state) { playerStateSubject.add( playerState.copyWith( playing: state.playing, @@ -34,15 +33,15 @@ class AbsPlatformAudioPlayer extends AbsAudioPlayer { ), ); }); - player.positionStream.distinct().listen((position) { + positionStream.listen((position) { final chapter = currentChapter; if (positionInBook <= (chapter?.start ?? Duration.zero) || - positionInBook <= (chapter?.end ?? Duration.zero)) { + positionInBook >= (chapter?.end ?? Duration.zero)) { final chapter = book?.findChapterAtTime(positionInBook); if (chapter != currentChapter) { print('当前章节时长: ${currentChapter?.duration}'); print('切换章节时长: ${chapter?.duration}'); - print('当前播放音轨时长: ${player.duration}'); + print('当前播放音轨时长: ${_player.duration}'); chapterStreamController.add(chapter); } } @@ -52,7 +51,11 @@ class AbsPlatformAudioPlayer extends AbsAudioPlayer { Duration get bufferedPosition => _player.bufferedPosition; @override - Stream get bufferedPositionStream => _player.bufferedPositionStream; + Stream get bufferedPositionStream => _player.bufferedPositionStream + .where( + (_) => _player.playerState.processingState == ProcessingState.buffering, + ) + .asBroadcastStream(); @override int get currentIndex => _player.currentIndex ?? 0; @@ -76,24 +79,53 @@ class AbsPlatformAudioPlayer extends AbsAudioPlayer { Stream get playingStream => _player.playingStream; @override - Duration get position => _player.position; + Duration get position => _addClippingStart(_player.position); + + Duration _addClippingStart(Duration position, {bool add = true}) { + if (_player.sequenceState.currentSource != null && + _player.sequenceState.currentSource is ClippingAudioSource) { + final currentSource = + _player.sequenceState.currentSource as ClippingAudioSource; + if (currentSource.start != null) { + return add + ? position + currentSource.start! + : position - currentSource.start!; + } + } + return position; + } @override - Stream get positionStream => _player.positionStream; + Stream get positionStream => + _player.positionStream.where((_) => _player.playing).map((position) { + return _addClippingStart(position); + }); @override Future seek(Duration position, {int? index}) async { - await _player.seek(position, index: index); + await _player.seek(_addClippingStart(position, add: false), index: index); } @override Future setPlayList( - List playlist, { + List<(Uri, Duration)> playlist, { int? index, Duration? position, + Duration? start, + Duration? end, }) async { - List audioSources = - playlist.map((uri) => AudioSource.uri(uri)).toList(); + List audioSources = start != null && start > Duration.zero || + end != null && end > Duration.zero + ? playlist + .map( + (item) => ClippingAudioSource( + child: AudioSource.uri(item.$1), + start: start, + end: end == null ? null : item.$2 - end, + ), + ) + .toList() + : playlist.map((item) => AudioSource.uri(item.$1)).toList(); await _player .setAudioSources( audioSources, diff --git a/lib/features/player/providers/abs_provider.dart b/lib/features/player/providers/abs_provider.dart index ad572a3..4fd3f8a 100644 --- a/lib/features/player/providers/abs_provider.dart +++ b/lib/features/player/providers/abs_provider.dart @@ -1,15 +1,17 @@ import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:just_audio/just_audio.dart'; -import 'package:just_audio_media_kit/just_audio_media_kit.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart' as api; import 'package:vaani/api/api_provider.dart'; +import 'package:vaani/api/library_item_provider.dart'; +import 'package:vaani/features/downloads/providers/download_manager.dart'; +import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart'; import 'package:vaani/features/player/core/abs_audio_handler.dart'; import 'package:vaani/features/player/core/abs_audio_player.dart' as core; import 'package:vaani/features/player/core/abs_audio_player_platform.dart'; +import 'package:vaani/features/settings/app_settings_provider.dart'; part 'abs_provider.g.dart'; @@ -46,27 +48,27 @@ Future configurePlayer(Ref ref) async { } // just_audio 播放器 -@Riverpod(keepAlive: true) -AudioPlayer audioPlayer(Ref ref) { - // 跳转到播放列表指定条目指定位置 - // prefetch-playlist=yes - JustAudioMediaKit.prefetchPlaylist = true; - // merge-files=yes - // cache=yes - // cache-pause-wait=60 +// @Riverpod(keepAlive: true) +// AudioPlayer audioPlayer(Ref ref) { +// // 跳转到播放列表指定条目指定位置 +// // prefetch-playlist=yes +// JustAudioMediaKit.prefetchPlaylist = true; +// // merge-files=yes +// // cache=yes +// // cache-pause-wait=60 - JustAudioMediaKit.ensureInitialized(); - return AudioPlayer(); -} +// JustAudioMediaKit.ensureInitialized(); +// return AudioPlayer(); +// } /// 音频播放器 riverpod状态 @Riverpod(keepAlive: true) class AbsPlayer extends _$AbsPlayer { @override core.AbsAudioPlayer build() { - final audioPlayer = ref.watch(audioPlayerProvider); + // final audioPlayer = ref.watch(audioPlayerProvider); // final player = AbsMpvAudioPlayer(); - final player = AbsPlatformAudioPlayer(audioPlayer); + final player = AbsPlatformAudioPlayer(); ref.onDispose(player.dispose); return player; } @@ -75,16 +77,47 @@ class AbsPlayer extends _$AbsPlayer { api.BookExpanded book, { Duration? initialPosition, }) async { - if (state.book == book) { + if (state.book == book || state.book?.libraryItemId == book.libraryItemId) { state.playOrPause(); return; } final api = ref.read(authenticatedApiProvider); + + final downloadManager = ref.read(simpleDownloadManagerProvider); + final libItem = + await ref.read(libraryItemProvider(book.libraryItemId).future); + final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem); + + var bookPlayerSettings = + ref.read(bookSettingsProvider(book.libraryItemId)).playerSettings; + var appPlayerSettings = ref.read(appSettingsProvider).playerSettings; + + var configurePlayerForEveryBook = + appPlayerSettings.configurePlayerForEveryBook; + + final bookSettings = ref.watch(bookSettingsProvider(book.libraryItemId)); await state.load( book, baseUrl: api.baseUrl, token: api.token!, initialPosition: initialPosition, + downloadedUris: downloadedUris, + start: bookSettings.playerSettings.skipChapterStart, + end: bookSettings.playerSettings.skipChapterEnd, + ); + // set the volume + await state.setVolume( + configurePlayerForEveryBook + ? bookPlayerSettings.preferredDefaultVolume ?? + appPlayerSettings.preferredDefaultVolume + : appPlayerSettings.preferredDefaultVolume, + ); + // set the speed + await state.setSpeed( + configurePlayerForEveryBook + ? bookPlayerSettings.preferredDefaultSpeed ?? + appPlayerSettings.preferredDefaultSpeed + : appPlayerSettings.preferredDefaultSpeed, ); await state.play(); } diff --git a/lib/features/player/providers/abs_provider.g.dart b/lib/features/player/providers/abs_provider.g.dart index 3c3d76b..6c27f3c 100644 --- a/lib/features/player/providers/abs_provider.g.dart +++ b/lib/features/player/providers/abs_provider.g.dart @@ -25,22 +25,6 @@ final configurePlayerProvider = FutureProvider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef ConfigurePlayerRef = FutureProviderRef; -String _$audioPlayerHash() => r'd9d6cea83d03e36fac43367b6a24fd9d3a53b2fa'; - -/// See also [audioPlayer]. -@ProviderFor(audioPlayer) -final audioPlayerProvider = Provider.internal( - audioPlayer, - name: r'audioPlayerProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$audioPlayerHash, - dependencies: null, - allTransitiveDependencies: null, -); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef AudioPlayerRef = ProviderRef; String _$isPlayerActiveHash() => r'4fca4af53a17dbcd7c8a98ce115bc11fa39b4cf9'; /// See also [isPlayerActive]. @@ -93,7 +77,7 @@ final currentChaptersProvider = @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef CurrentChaptersRef = AutoDisposeProviderRef>; -String _$absPlayerHash() => r'2ec8952e1ec764b02239ff7c26144040e460976a'; +String _$absPlayerHash() => r'dfb4a8e9778d44143ec7589a99c6295c32c64c4a'; /// 音频播放器 riverpod状态 /// diff --git a/lib/features/player/view/widgets/player_progress_bar.dart b/lib/features/player/view/widgets/player_progress_bar.dart index b9b8c2f..ad4ad7c 100644 --- a/lib/features/player/view/widgets/player_progress_bar.dart +++ b/lib/features/player/view/widgets/player_progress_bar.dart @@ -33,12 +33,14 @@ class AudiobookChapterProgressBar extends HookConsumerWidget { ? null : (player.bufferedPositionInBook - currentChapter.start); + final progress = + currentChapterProgress ?? position.data ?? const Duration(seconds: 0); + final total = currentChapter == null + ? player.book?.duration ?? const Duration(seconds: 0) + : currentChapter.end - currentChapter.start; return ProgressBar( - progress: - currentChapterProgress ?? position.data ?? const Duration(seconds: 0), - total: currentChapter == null - ? player.book?.duration ?? const Duration(seconds: 0) - : currentChapter.end - currentChapter.start, + progress: progress, + total: total, // ! TODO add onSeek onSeek: (duration) { player.seekInBook( diff --git a/lib/framework.dart b/lib/framework.dart index 2eb8d1f..adb88d0 100644 --- a/lib/framework.dart +++ b/lib/framework.dart @@ -21,7 +21,7 @@ class Framework extends ConsumerWidget { ref.watch(simpleDownloadManagerProvider); if (Helper.isAndroid()) ref.watch(shakeDetectorProvider); ref.watch(sleepTimerProvider); - ref.watch(skipStartEndProvider); + // ref.watch(skipStartEndProvider); ref.watch(playbackReporterProvider); } catch (e) { debugPrintStack(stackTrace: StackTrace.current, label: e.toString()); diff --git a/pubspec.lock b/pubspec.lock index 7874d54..d0f0eb6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -838,10 +838,10 @@ packages: dependency: transitive description: name: media_kit - sha256: "2a207ea7baf1a2ea2ff2016d512e572ca6fc02a937769effb5c27b4d682b4a53" + sha256: ae9e79597500c7ad6083a3c7b7b7544ddabfceacce7ae5c9709b0ec16a5d6643 url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.6" media_kit_libs_linux: dependency: "direct main" description: @@ -1442,10 +1442,10 @@ packages: dependency: transitive description: name: uri_parser - sha256: ff4d2c720aca3f4f7d5445e23b11b2d15ef8af5ddce5164643f38ff962dcb270 + sha256: "051c62e5f693de98ca9f130ee707f8916e2266945565926be3ff20659f7853ce" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.2" url_launcher: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 49a1068..6daded7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,8 @@ dependencies: # path: just_audio_background # just_audio_windows: ^0.2.2 just_audio_media_kit: ^2.0.4 + # just_audio_media_kit: + # path: ./just_audio_media_kit media_kit_libs_linux: any media_kit_libs_windows_audio: any # media_kit: ^1.2.3 # Primary package.