From 20a3b95edc85ca049dfdabd0c597a19b2f8e4ba3 Mon Sep 17 00:00:00 2001 From: rang <378694192@qq.com> Date: Tue, 9 Dec 2025 17:26:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=9C=AA=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/library_item_actions.dart | 4 +- .../view/library_item_hero_section.dart | 2 +- .../core/playback_reporter.dart | 2 +- .../core/playback_reporter_session.dart | 2 +- .../providers/playback_reporter_provider.dart | 2 +- .../playback_reporter_provider.g.dart | 2 +- .../player/core/abs_audio_handler.dart | 3 +- .../player/core/abs_audio_player.dart | 364 ++++++++++++-- .../player/core/abs_audio_player_mpv.dart} | 11 +- .../core/abs_audio_player_platform.dart} | 14 +- .../player/core/audiobook_player.dart | 468 ------------------ lib/features/player/core/init.dart | 11 +- lib/features/player/core/player_status.dart | 58 --- .../player/providers/abs_provider.dart | 23 +- .../player/providers/abs_provider.g.dart | 33 +- .../player/providers/audiobook_player.dart | 108 ---- .../providers/currently_playing_provider.dart | 80 --- .../providers/player_status_provider.dart | 40 -- .../view/mini_player_bottom_padding.dart | 2 +- .../player/view/player_expanded_desktop.dart | 83 ++-- .../player/view/player_minimized.dart | 222 +++++---- .../widgets/audiobook_player_seek_button.dart | 2 +- .../audiobook_player_seek_chapter_button.dart | 2 +- .../widgets/chapter_selection_button.dart | 2 +- .../widgets/player_player_pause_button.dart | 4 +- .../view/widgets/player_progress_bar.dart | 4 +- .../widgets/player_speed_adjust_button.dart | 2 +- .../player/view/widgets/speed_selector.dart | 2 +- .../shake_detector_provider.dart | 9 +- .../shake_detector_provider.g.dart | 2 +- .../skip_start_end/core/skip_start_end.dart | 2 +- .../providers/skip_start_end_provider.dart | 2 +- .../providers/skip_start_end_provider.g.dart | 2 +- .../sleep_timer/core/sleep_timer.dart | 2 +- .../providers/sleep_timer_provider.dart | 4 +- .../providers/sleep_timer_provider.g.dart | 2 +- lib/framework.dart | 8 +- lib/main.dart | 3 +- lib/pages/player_page.dart | 3 +- lib/router/router.dart | 1 - lib/router/scaffold_with_nav_bar.dart | 2 +- lib/shared/audio_player.dart | 336 ------------- lib/shared/widgets/drawer.dart | 11 +- lib/shared/widgets/shelves/book_shelf.dart | 2 +- lib/shared/widgets/tray_manager.dart | 6 +- macos/Flutter/GeneratedPluginRegistrant.swift | 4 - pubspec.lock | 120 +---- pubspec.yaml | 36 +- 48 files changed, 637 insertions(+), 1472 deletions(-) rename lib/{shared/audio_player_mpv.dart => features/player/core/abs_audio_player_mpv.dart} (88%) rename lib/{shared/audio_player_platform.dart => features/player/core/abs_audio_player_platform.dart} (82%) delete mode 100644 lib/features/player/core/audiobook_player.dart delete mode 100644 lib/features/player/core/player_status.dart delete mode 100644 lib/features/player/providers/audiobook_player.dart delete mode 100644 lib/features/player/providers/currently_playing_provider.dart delete mode 100644 lib/features/player/providers/player_status_provider.dart delete mode 100644 lib/shared/audio_player.dart diff --git a/lib/features/item_viewer/view/library_item_actions.dart b/lib/features/item_viewer/view/library_item_actions.dart index d56459f..5dca8af 100644 --- a/lib/features/item_viewer/view/library_item_actions.dart +++ b/lib/features/item_viewer/view/library_item_actions.dart @@ -467,8 +467,8 @@ class _LibraryItemPlayButton extends HookConsumerWidget { return ElevatedButton.icon( onPressed: () { currentBook?.libraryItemId == book.libraryItemId - ? ref.read(absAudioPlayerProvider).playOrPause() - : ref.read(absAudioPlayerProvider.notifier).load( + ? ref.read(audioPlayerProvider).playOrPause() + : ref.read(audioPlayerProvider.notifier).load( book, initialPosition: userMediaProgress?.currentTime, ); diff --git a/lib/features/item_viewer/view/library_item_hero_section.dart b/lib/features/item_viewer/view/library_item_hero_section.dart index a1cc384..9e07c0a 100644 --- a/lib/features/item_viewer/view/library_item_hero_section.dart +++ b/lib/features/item_viewer/view/library_item_hero_section.dart @@ -139,7 +139,7 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final player = ref.watch(absAudioPlayerProvider); + final player = ref.watch(audioPlayerProvider); final libraryItem = ref.watch(libraryItemProvider(id)).valueOrNull; if (libraryItem == null) { return const SizedBox.shrink(); diff --git a/lib/features/playback_reporting/core/playback_reporter.dart b/lib/features/playback_reporting/core/playback_reporter.dart index c83426f..68cc3b2 100644 --- a/lib/features/playback_reporting/core/playback_reporter.dart +++ b/lib/features/playback_reporting/core/playback_reporter.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; -import 'package:vaani/shared/audio_player.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; import 'package:vaani/shared/extensions/obfuscation.dart'; final _logger = Logger('PlaybackReporter'); diff --git a/lib/features/playback_reporting/core/playback_reporter_session.dart b/lib/features/playback_reporting/core/playback_reporter_session.dart index 3b0842f..d157fcc 100644 --- a/lib/features/playback_reporting/core/playback_reporter_session.dart +++ b/lib/features/playback_reporting/core/playback_reporter_session.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:http/http.dart' as http; import 'package:logging/logging.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; -import 'package:vaani/shared/audio_player.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; import 'package:vaani/shared/extensions/obfuscation.dart'; final _logger = Logger('PlaybackReporter'); diff --git a/lib/features/playback_reporting/providers/playback_reporter_provider.dart b/lib/features/playback_reporting/providers/playback_reporter_provider.dart index a85cf1a..7851675 100644 --- a/lib/features/playback_reporting/providers/playback_reporter_provider.dart +++ b/lib/features/playback_reporting/providers/playback_reporter_provider.dart @@ -13,7 +13,7 @@ class PlaybackReporter extends _$PlaybackReporter { @override Future build() async { final playerSettings = ref.watch(appSettingsProvider).playerSettings; - final player = ref.watch(absAudioPlayerProvider); + final player = ref.watch(audioPlayerProvider); final api = ref.watch(authenticatedApiProvider); final reporter = core.PlaybackReporter( diff --git a/lib/features/playback_reporting/providers/playback_reporter_provider.g.dart b/lib/features/playback_reporting/providers/playback_reporter_provider.g.dart index 981782c..d1d3a5d 100644 --- a/lib/features/playback_reporting/providers/playback_reporter_provider.g.dart +++ b/lib/features/playback_reporting/providers/playback_reporter_provider.g.dart @@ -6,7 +6,7 @@ part of 'playback_reporter_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$playbackReporterHash() => r'13936a7d616e9055388d23fa361519b749c524a3'; +String _$playbackReporterHash() => r'1cdf5cbc614c05c240d28bf0ec740d3899fd957a'; /// See also [PlaybackReporter]. @ProviderFor(PlaybackReporter) diff --git a/lib/features/player/core/abs_audio_handler.dart b/lib/features/player/core/abs_audio_handler.dart index acc9e81..d4488ec 100644 --- a/lib/features/player/core/abs_audio_handler.dart +++ b/lib/features/player/core/abs_audio_handler.dart @@ -1,6 +1,7 @@ import 'package:audio_service/audio_service.dart'; -import 'package:vaani/shared/audio_player.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; +// 对接audio_service class AbsAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { final AbsAudioPlayer player; diff --git a/lib/features/player/core/abs_audio_player.dart b/lib/features/player/core/abs_audio_player.dart index 4450782..c619336 100644 --- a/lib/features/player/core/abs_audio_player.dart +++ b/lib/features/player/core/abs_audio_player.dart @@ -1,33 +1,337 @@ -// // ignore_for_file: public_member_api_docs, sort_constructors_first -// // import 'package:just_audio/just_audio.dart'; -// import 'package:shelfsdk/audiobookshelf_api.dart' as api; +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:async'; -// class AbsPlayerState { -// api.BookExpanded? book; -// // 当前章节 -// final api.BookChapter? currentChapter; -// // 当前音轨序号 -// final int currentIndex; -// final bool playing; +import 'package:audio_service/audio_service.dart'; +import 'package:collection/collection.dart'; +import 'package:just_audio/just_audio.dart'; +import 'package:logging/logging.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:shelfsdk/audiobookshelf_api.dart'; -// AbsPlayerState({ -// this.book, -// this.currentChapter, -// this.currentIndex = 0, -// this.playing = false, -// }); +import 'package:vaani/features/settings/app_settings_provider.dart'; +import 'package:vaani/features/settings/models/app_settings.dart'; +import 'package:vaani/shared/extensions/chapter.dart'; +import 'package:vaani/shared/extensions/model_conversions.dart'; -// AbsPlayerState copyWith({ -// api.BookExpanded? book, -// api.BookChapter? currentChapter, -// int? currentIndex, -// bool? playing, -// }) { -// return AbsPlayerState( -// book: book ?? this.book, -// currentChapter: currentChapter ?? this.currentChapter, -// currentIndex: currentIndex ?? this.currentIndex, -// playing: playing ?? this.playing, -// ); -// } -// } +final offset = Duration(milliseconds: 10); + +final _logger = Logger('AbsAudioPlayer'); + +/// 音频播放器抽象类 +abstract class AbsAudioPlayer { + final _mediaItemController = BehaviorSubject.seeded(null); + final playerStateSubject = + BehaviorSubject.seeded(AbsPlayerState(false, AbsProcessingState.idle)); + final _bookStreamController = BehaviorSubject.seeded(null); + final chapterStreamController = BehaviorSubject.seeded(null); + + BookExpanded? get book => _bookStreamController.nvalue; + BookChapter? get currentChapter => chapterStreamController.nvalue; + AbsPlayerState get playerState => playerStateSubject.value; + Stream get mediaItemStream => _mediaItemController.stream; + Stream get playerStateStream => playerStateSubject.stream; + + Future load( + BookExpanded book, { + required Uri baseUrl, + required String token, + Duration? initialPosition, + List? downloadedUris, + }) async { + if (_bookStreamController.nvalue == book) { + _logger.info('Book is the same, doing nothing'); + return; + } + _bookStreamController.add(book); + final appSettings = loadOrCreateAppSettings(); + + final currentTrack = book.findTrackAtTime(initialPosition ?? Duration.zero); + final indexTrack = book.tracks.indexOf(currentTrack); + final positionInTrack = initialPosition != null + ? initialPosition - currentTrack.startOffset + : null; + final title = appSettings.notificationSettings.primaryTitle + .formatNotificationTitle(book); + final artist = appSettings.notificationSettings.secondaryTitle + .formatNotificationTitle(book); + chapterStreamController + .add(book.findChapterAtTime(initialPosition ?? Duration.zero)); + final item = MediaItem( + id: book.libraryItemId, + title: title, + artist: artist, + duration: currentChapter?.duration ?? book.duration, + artUri: Uri.parse( + '$baseUrl/api/items/${book.libraryItemId}/cover?token=$token', + ), + ); + _mediaItemController.sink.add(item); + final playlist = book.tracks + .map( + (track) => _getUri(currentTrack, downloadedUris, + baseUrl: baseUrl, token: token), + ) + .toList(); + await setPlayList(playlist, index: indexTrack, position: positionInTrack); + } + + Future setPlayList( + List playlist, { + int? index, + Duration? position, + }); + Future play(); + Future pause(); + Future playOrPause(); + + // 跳到下一章 + Future next() async { + final chapter = currentChapter; + if (book == null || chapter == null) { + return; + } + final chapterIndex = book!.chapters.indexOf(chapter); + if (chapterIndex < book!.chapters.length - 1) { + final nextChapter = book!.chapters[chapterIndex + 1]; + await seekInBook(nextChapter.start + offset); + } + } + + // 跳到上一章 + Future previous() async { + final chapter = currentChapter; + if (book == null || chapter == null) { + return; + } + final currentIndex = book!.chapters.indexOf(chapter); + if (currentIndex > 0) { + final prevChapter = book!.chapters[currentIndex - 1]; + await seekInBook(prevChapter.start + offset); + } else { + // 已经是第一章,回到开头 + await seekInBook(Duration.zero); + } + } + + Future seek(Duration position, {int? index}); + Future seekInBook(Duration position) async { + if (book == null) return; + // 找到目标位置所在音轨和音轨内的位置 + final track = book!.findTrackAtTime(position); + final index = book!.tracks.indexOf(track); + Duration positionInTrack = position - track.startOffset; + if (positionInTrack <= Duration.zero) { + positionInTrack = offset; + } + // 切换到目标音轨具体位置 + await seek(positionInTrack, index: index); + } + + Future setSpeed(double speed); + Future setVolume(double volume); + Future switchChapter(int chapterId) async { + if (book == null) return; + + final chapter = book!.chapters.firstWhere( + (ch) => ch.id == chapterId, + orElse: () => throw Exception('Chapter not found'), + ); + await seekInBook(chapter.start + offset); + } + + bool get playing => playerState.playing; + Stream get playingStream; + Stream get bookStream => _bookStreamController.stream; + Stream get chapterStream => chapterStreamController.stream; + + int get currentIndex; + double get speed; + + Duration get position; + Stream get positionStream; + + Duration get positionInChapter { + final globalPosition = positionInBook; + return globalPosition - + (book?.findChapterAtTime(globalPosition).start ?? Duration.zero); + } + + Duration get positionInBook => + position + (book?.tracks[currentIndex].startOffset ?? Duration.zero); + + Stream get positionInChapterStream => + positionStream.map((position) { + return positionInChapter; + }); + + Stream get positionInBookStream => positionStream.map((position) { + return positionInBook; + }); + + Duration get bufferedPosition; + Stream get bufferedPositionStream; + Duration get bufferedPositionInBook => + bufferedPosition + + (book?.tracks[currentIndex].startOffset ?? Duration.zero); + Stream get bufferedPositionInBookStream => + bufferedPositionStream.map((position) { + return bufferedPositionInBook; + }); + + dispose() { + _mediaItemController.close(); + playerStateSubject.close(); + _bookStreamController.close(); + chapterStreamController.close(); + } +} + +/// Enumerates the different processing states of a player. +enum AbsProcessingState { + /// The player has not loaded an [AudioSource]. + idle, + + /// The player is loading an [AudioSource]. + loading, + + /// The player is buffering audio and unable to play. + buffering, + + /// The player is has enough audio buffered and is able to play. + ready, + + /// The player has reached the end of the audio. + completed, +} + +/// Encapsulates the playing and processing states. These two states vary +/// orthogonally, and so if [processingState] is [ProcessingState.buffering], +/// you can check [playing] to determine whether the buffering occurred while +/// the player was playing or while the player was paused. +class AbsPlayerState { + /// Whether the player will play when [processingState] is + /// [ProcessingState.ready]. + final bool playing; + + /// The current processing state of the player. + final AbsProcessingState processingState; + + AbsPlayerState(this.playing, this.processingState); + + @override + String toString() => 'playing=$playing,processingState=$processingState'; + + @override + int get hashCode => Object.hash(playing, processingState); + + @override + bool operator ==(Object other) => + other.runtimeType == runtimeType && + other is PlayerState && + other.playing == playing && + other.processingState == processingState; + + AbsPlayerState copyWith({ + bool? playing, + AbsProcessingState? processingState, + }) { + return AbsPlayerState( + playing ?? this.playing, + processingState ?? this.processingState, + ); + } +} + +Uri _getUri( + AudioTrack track, + List? downloadedUris, { + required Uri baseUrl, + required String token, +}) { + // check if the track is in the downloadedUris + final uri = downloadedUris?.firstWhereOrNull( + (element) { + return element.pathSegments.last == track.metadata?.filename; + }, + ); + + return uri ?? + Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token'); +} + +/// Backwards compatible extensions on rxdart's ValueStream +extension _ValueStreamExtension on ValueStream { + /// Backwards compatible version of valueOrNull. + T? get nvalue => hasValue ? value : null; +} + +extension FormatNotificationTitle on String { + String formatNotificationTitle(BookExpanded book) { + return replaceAllMapped( + RegExp(r'\$(\w+)'), + (match) { + final type = match.group(1); + return NotificationTitleType.values + .firstWhere((element) => element.name == type) + .extractFrom(book) ?? + match.group(0) ?? + ''; + }, + ); + } +} + +extension NotificationTitleUtils on NotificationTitleType { + String? extractFrom(BookExpanded book) { + var bookMetadataExpanded = book.metadata.asBookMetadataExpanded; + switch (this) { + case NotificationTitleType.bookTitle: + return bookMetadataExpanded.title; + case NotificationTitleType.chapterTitle: + // TODO: implement chapter title; depends on https://github.com/Dr-Blank/Vaani/issues/2 + return bookMetadataExpanded.title; + case NotificationTitleType.author: + return bookMetadataExpanded.authorName; + case NotificationTitleType.narrator: + return bookMetadataExpanded.narratorName; + case NotificationTitleType.series: + return bookMetadataExpanded.seriesName; + case NotificationTitleType.subtitle: + return bookMetadataExpanded.subtitle; + case NotificationTitleType.year: + return bookMetadataExpanded.publishedYear; + } + } +} + +extension BookExpandedExtension on BookExpanded { + BookChapter findChapterAtTime(Duration position) { + return chapters.firstWhere( + (element) { + return element.start <= position && element.end >= position + offset; + }, + orElse: () => chapters.first, + ); + } + + AudioTrack findTrackAtTime(Duration position) { + return tracks.firstWhere( + (element) { + return element.startOffset <= position && + element.startOffset + element.duration >= position + offset; + }, + orElse: () => tracks.first, + ); + } + + int findTrackIndexAtTime(Duration position) { + return tracks.indexWhere((element) { + return element.startOffset <= position && + element.startOffset + element.duration >= position + offset; + }); + } + + Duration getTrackStartOffset(int index) { + return tracks[index].startOffset; + } +} diff --git a/lib/shared/audio_player_mpv.dart b/lib/features/player/core/abs_audio_player_mpv.dart similarity index 88% rename from lib/shared/audio_player_mpv.dart rename to lib/features/player/core/abs_audio_player_mpv.dart index b442e54..2ea7ea9 100644 --- a/lib/shared/audio_player_mpv.dart +++ b/lib/features/player/core/abs_audio_player_mpv.dart @@ -1,11 +1,14 @@ import 'dart:async'; import 'package:media_kit/media_kit.dart' hide PlayerState; -import 'package:vaani/shared/audio_player.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; +/// 音频播放器 mpv全平台 (media_kit) class AbsMpvAudioPlayer extends AbsAudioPlayer { - final player = Player(); + late Player player; AbsMpvAudioPlayer() { + MediaKit.ensureInitialized(); + player = Player(); player.stream.playing.listen((playing) { final state = playerState; playerStateSubject.add( @@ -60,7 +63,9 @@ class AbsMpvAudioPlayer extends AbsAudioPlayer { @override Future seek(Duration position, {int? index}) async { if (index != null) { + final playing = this.playing; await player.jump(index); + if (!playing) await player.pause(); } await player.seek(position); } @@ -93,7 +98,7 @@ class AbsMpvAudioPlayer extends AbsAudioPlayer { @override Future setVolume(double volume) async { - await player.setVolume(volume); + await player.setVolume(volume * 100); } @override diff --git a/lib/shared/audio_player_platform.dart b/lib/features/player/core/abs_audio_player_platform.dart similarity index 82% rename from lib/shared/audio_player_platform.dart rename to lib/features/player/core/abs_audio_player_platform.dart index 7720e53..e9fc341 100644 --- a/lib/shared/audio_player_platform.dart +++ b/lib/features/player/core/abs_audio_player_platform.dart @@ -1,12 +1,16 @@ 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:vaani/shared/audio_player.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; final _logger = Logger('AbsPlatformAudioPlayer'); +/// 音频播放器 平台ios,macos,android (just_audio) class AbsPlatformAudioPlayer extends AbsAudioPlayer { - final AudioPlayer player = AudioPlayer(); + late final AudioPlayer player; AbsPlatformAudioPlayer() { + JustAudioMediaKit.ensureInitialized(); + player = AudioPlayer(); player.playerStateStream.listen((state) { playerStateSubject.add( playerState.copyWith( @@ -21,6 +25,12 @@ class AbsPlatformAudioPlayer extends AbsAudioPlayer { ), ); }); + player.positionStream.distinct().listen((position) { + final chapter = book?.findChapterAtTime(positionInBook); + if (chapter != currentChapter) { + chapterStreamController.add(chapter); + } + }); } @override Duration get bufferedPosition => player.bufferedPosition; diff --git a/lib/features/player/core/audiobook_player.dart b/lib/features/player/core/audiobook_player.dart deleted file mode 100644 index 06d62b8..0000000 --- a/lib/features/player/core/audiobook_player.dart +++ /dev/null @@ -1,468 +0,0 @@ -// // my_audio_handler.dart -// import 'dart:io'; - -// import 'package:audio_service/audio_service.dart'; -// import 'package:collection/collection.dart'; -// import 'package:flutter/foundation.dart'; -// import 'package:hooks_riverpod/hooks_riverpod.dart'; -// import 'package:just_audio/just_audio.dart'; -// import 'package:logging/logging.dart'; -// import 'package:rxdart/rxdart.dart'; -// import 'package:shelfsdk/audiobookshelf_api.dart' hide NotificationSettings; -// import 'package:vaani/features/player/core/player_status.dart' as core; -// import 'package:vaani/features/player/providers/player_status_provider.dart'; -// import 'package:vaani/features/settings/app_settings_provider.dart'; -// import 'package:vaani/features/settings/models/app_settings.dart'; -// import 'package:vaani/shared/extensions/chapter.dart'; -// import 'package:vaani/shared/extensions/model_conversions.dart'; - -// // add a small offset so the display does not show the previous chapter for a split second -// final offset = Duration(milliseconds: 10); - -// final _logger = Logger('AudiobookPlayer'); - -// class AbsAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { -// final AudioPlayer _player = AudioPlayer(); -// final Ref ref; - -// BookExpanded? _book; -// BookExpanded? get book => _book; - -// late NotificationSettings notificationSettings; - -// final _currentChapterObject = BehaviorSubject.seeded(null); -// AbsAudioHandler(this.ref) { -// notificationSettings = ref.read(appSettingsProvider).notificationSettings; -// ref.listen(appSettingsProvider, (a, b) { -// if (a?.notificationSettings != b.notificationSettings) { -// notificationSettings = b.notificationSettings; -// } -// }); -// final statusNotifier = ref.read(playerStatusProvider.notifier); - -// // 转发播放状态 -// _player.playbackEventStream.map(_transformEvent).pipe(playbackState); -// _player.playerStateStream.listen((event) { -// if (event.playing) { -// statusNotifier.setPlayStatusVerify(core.PlayStatus.playing); -// } else { -// statusNotifier.setPlayStatusVerify(core.PlayStatus.paused); -// } -// }); -// _player.positionStream.distinct().listen((position) { -// final chapter = _book?.findChapterAtTime(positionInBook); -// if (chapter != currentChapter) { -// if (mediaItem.hasValue && chapter != null) { -// // updateMediaItem( -// // mediaItem.value!.copyWith( -// // duration: chapter.duration, -// // displayTitle: chapter.title, -// // ), -// // ); -// } -// _currentChapterObject.sink.add(chapter); -// } -// }); -// } - -// // 加载有声书 -// Future setSourceAudiobook( -// BookExpanded book, { -// bool preload = true, -// required Uri baseUrl, -// required String token, -// Duration? initialPosition, -// List? downloadedUris, -// required double volume, -// required double speed, -// }) async { -// final appSettings = loadOrCreateAppSettings(); -// // if (book == null) { -// // _book = null; -// // _logger.info('Book is null, stopping player'); -// // return stop(); -// // } -// if (_book == book) { -// _logger.info('Book is the same, doing nothing'); -// return; -// } -// _book = book; - -// final trackToPlay = book.findTrackAtTime(initialPosition ?? Duration.zero); -// final trackToChapter = -// book.findChapterAtTime(initialPosition ?? Duration.zero); -// final initialIndex = book.tracks.indexOf(trackToPlay); -// final initialPositionInTrack = initialPosition != null -// ? initialPosition - trackToPlay.startOffset -// : null; -// final title = appSettings.notificationSettings.primaryTitle -// .formatNotificationTitle(book); -// final album = appSettings.notificationSettings.secondaryTitle -// .formatNotificationTitle(book); - -// // 添加所有音轨 -// List audioSources = book.tracks -// .map( -// (track) => AudioSource.uri( -// _getUri(track, downloadedUris, baseUrl: baseUrl, token: token), -// ), -// ) -// .toList(); - -// final item = MediaItem( -// id: book.libraryItemId, -// title: title, -// album: album, -// displayTitle: title, -// displaySubtitle: album, -// duration: trackToChapter.duration, -// artUri: Uri.parse( -// '$baseUrl/api/items/${book.libraryItemId}/cover?token=$token', -// ), -// ); -// mediaItem.add(item); -// await _player -// .setAudioSources( -// audioSources, -// preload: preload, -// initialIndex: initialIndex, -// initialPosition: initialPositionInTrack, -// ) -// .catchError((error) { -// _logger.shout('Error in setting audio source: $error'); -// return null; -// }); -// await player.seek(initialPositionInTrack, index: initialIndex); -// // _player.seek(initialPositionInTrack, index: initialIndex); -// setVolume(volume); -// setSpeed(speed); -// await play(); - -// // 恢复上次播放位置(如果有) -// // if (initialPosition != null) { -// // await seekInBook(initialPosition); -// // } -// } - -// // // 音轨切换处理 -// // void _onTrackChanged(int trackIndex) { -// // if (_book == null) return; - -// // // 可以在这里处理音轨切换逻辑,比如预加载下一音轨 -// // // print('切换到音轨: ${_book!.tracks[trackIndex].title}'); -// // } - -// // 核心功能:跳转到指定章节 -// Future skipToChapter(int chapterId) async { -// if (_book == null) return; - -// final chapter = _book!.chapters.firstWhere( -// (ch) => ch.id == chapterId, -// orElse: () => throw Exception('Chapter not found'), -// ); -// await seekInBook(chapter.start + offset); -// } - -// BookExpanded? get Book => _book; - -// // 当前音轨 -// AudioTrack? get currentTrack { -// if (_book == null || _player.currentIndex == null) { -// return null; -// } -// return _book!.tracks[_player.currentIndex!]; -// } - -// // 当前章节 -// BookChapter? get currentChapter { -// return _currentChapterObject.value; -// } - -// Duration get position => _player.position; -// Duration get positionInChapter { -// return _player.position + -// (currentTrack?.startOffset ?? Duration.zero) - -// (currentChapter?.start ?? Duration.zero); -// } - -// Duration get positionInBook { -// return _player.position + (currentTrack?.startOffset ?? Duration.zero); -// } - -// Duration get bufferedPositionInBook { -// return _player.bufferedPosition + -// (currentTrack?.startOffset ?? Duration.zero); -// } - -// Duration? get chapterDuration => currentChapter?.duration; - -// Stream get playerStateStream => _player.playerStateStream; - -// Stream get positionStream => _player.positionStream; - -// Stream get positionStreamInBook { -// return _player.positionStream.map((position) { -// return position + (currentTrack?.startOffset ?? Duration.zero); -// }); -// } - -// Stream get slowPositionStreamInBook { -// final superPositionStream = _player.createPositionStream( -// steps: 100, -// minPeriod: const Duration(milliseconds: 500), -// maxPeriod: const Duration(seconds: 1), -// ); -// return superPositionStream.map((position) { -// return position + (currentTrack?.startOffset ?? Duration.zero); -// }); -// } - -// Stream get bufferedPositionStreamInBook { -// return _player.bufferedPositionStream.map((position) { -// return position + (currentTrack?.startOffset ?? Duration.zero); -// }); -// } - -// Stream get positionStreamInChapter { -// return _player.positionStream.distinct().map((position) { -// return position + -// (currentTrack?.startOffset ?? Duration.zero) - -// (currentChapter?.start ?? Duration.zero); -// }); -// } - -// Stream get chapterStream => _currentChapterObject.stream; - -// Future togglePlayPause() async { -// // check if book is set -// if (_book == null) { -// _logger.warning('No book is set, not toggling play/pause'); -// } -// return switch (_player.playerState) { -// PlayerState(playing: var isPlaying) => isPlaying ? pause() : play(), -// }; -// } - -// // 播放控制方法 -// @override -// Future play() async { -// await _player.play(); -// } - -// @override -// Future pause() async { -// await _player.pause(); -// } - -// // 重写上一曲/下一曲为章节导航 -// @override -// Future skipToNext() async { -// if (_book == null) { -// // 回退到默认行为 -// return _player.seekToNext(); -// } -// final chapter = currentChapter; -// if (chapter == null) { -// // 回退到默认行为 -// return _player.seekToNext(); -// } -// final chapterIndex = _book!.chapters.indexOf(chapter); -// if (chapterIndex < _book!.chapters.length - 1) { -// // 跳到下一章 -// final nextChapter = _book!.chapters[chapterIndex + 1]; -// await skipToChapter(nextChapter.id); -// } -// } - -// @override -// Future skipToPrevious() async { -// final chapter = currentChapter; -// if (_book == null || chapter == null) { -// return _player.seekToPrevious(); -// } -// final currentIndex = _book!.chapters.indexOf(chapter); -// if (currentIndex > 0) { -// // 跳到上一章 -// final prevChapter = _book!.chapters[currentIndex - 1]; -// await skipToChapter(prevChapter.id); -// } else { -// // 已经是第一章,回到开头 -// await seekInBook(Duration.zero); -// } -// } - -// @override -// Future seek(Duration position) async { -// // 这个 position 是当前音轨内的位置,我们不直接使用 -// // 而是通过全局位置来控制 -// final track = currentTrack; -// Duration startOffset = Duration.zero; -// if (track != null) { -// startOffset = track.startOffset; -// } -// await seekInBook(startOffset + position); -// } - -// Future setVolume(double volume) async { -// await _player.setVolume(volume); -// } - -// @override -// Future setSpeed(double speed) async { -// await _player.setSpeed(speed); -// } - -// // 核心功能:跳转到全局时间位置 -// Future seekInBook(Duration globalPosition) async { -// if (_book == null) return; -// // 找到目标音轨和在音轨内的位置 -// final track = _book!.findTrackAtTime(globalPosition); -// final index = _book!.tracks.indexOf(track); -// Duration positionInTrack = globalPosition - track.startOffset; -// if (positionInTrack < Duration.zero) { -// positionInTrack = Duration.zero; -// } -// // 切换到目标音轨具体位置 -// await _player.seek(positionInTrack, index: index); -// } - -// AudioPlayer get player => _player; -// PlaybackState _transformEvent(PlaybackEvent event) { -// return PlaybackState( -// controls: [ -// if ((kIsWeb || !Platform.isAndroid) && -// notificationSettings.mediaControls -// .contains(NotificationMediaControl.skipToPreviousChapter)) -// MediaControl.skipToPrevious, -// if (notificationSettings.mediaControls -// .contains(NotificationMediaControl.rewind)) -// MediaControl.rewind, -// if (_player.playing) MediaControl.pause else MediaControl.play, -// if (notificationSettings.mediaControls -// .contains(NotificationMediaControl.fastForward)) -// MediaControl.fastForward, -// if ((kIsWeb || !Platform.isAndroid) && -// notificationSettings.mediaControls -// .contains(NotificationMediaControl.skipToNextChapter)) -// MediaControl.skipToNext, -// if (notificationSettings.mediaControls -// .contains(NotificationMediaControl.stop)) -// MediaControl.stop, -// ], -// systemActions: { -// // if (kIsWeb || !Platform.isAndroid) MediaAction.skipToPrevious, -// // MediaAction.rewind, -// MediaAction.seek, -// MediaAction.seekForward, -// MediaAction.seekBackward, -// // MediaAction.fastForward, -// // MediaAction.stop, -// // MediaAction.setSpeed, -// // if (kIsWeb || !Platform.isAndroid) MediaAction.skipToNext, -// }, -// androidCompactActionIndices: const [1, 2, 3], -// processingState: const { -// ProcessingState.idle: AudioProcessingState.idle, -// ProcessingState.loading: AudioProcessingState.loading, -// ProcessingState.buffering: AudioProcessingState.buffering, -// ProcessingState.ready: AudioProcessingState.ready, -// ProcessingState.completed: AudioProcessingState.completed, -// }[_player.processingState] ?? -// AudioProcessingState.idle, -// playing: _player.playing, -// updatePosition: positionInChapter, -// bufferedPosition: _player.bufferedPosition, -// speed: _player.speed, -// queueIndex: event.currentIndex, -// captioningEnabled: false, -// ); -// } -// } - -// Uri _getUri( -// AudioTrack track, -// List? downloadedUris, { -// required Uri baseUrl, -// required String token, -// }) { -// // check if the track is in the downloadedUris -// final uri = downloadedUris?.firstWhereOrNull( -// (element) { -// return element.pathSegments.last == track.metadata?.filename; -// }, -// ); - -// return uri ?? -// Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token'); -// } - -// extension FormatNotificationTitle on String { -// String formatNotificationTitle(BookExpanded book) { -// return replaceAllMapped( -// RegExp(r'\$(\w+)'), -// (match) { -// final type = match.group(1); -// return NotificationTitleType.values -// .firstWhere((element) => element.name == type) -// .extractFrom(book) ?? -// match.group(0) ?? -// ''; -// }, -// ); -// } -// } - -// extension NotificationTitleUtils on NotificationTitleType { -// String? extractFrom(BookExpanded book) { -// var bookMetadataExpanded = book.metadata.asBookMetadataExpanded; -// switch (this) { -// case NotificationTitleType.bookTitle: -// return bookMetadataExpanded.title; -// case NotificationTitleType.chapterTitle: -// // TODO: implement chapter title; depends on https://github.com/Dr-Blank/Vaani/issues/2 -// return bookMetadataExpanded.title; -// case NotificationTitleType.author: -// return bookMetadataExpanded.authorName; -// case NotificationTitleType.narrator: -// return bookMetadataExpanded.narratorName; -// case NotificationTitleType.series: -// return bookMetadataExpanded.seriesName; -// case NotificationTitleType.subtitle: -// return bookMetadataExpanded.subtitle; -// case NotificationTitleType.year: -// return bookMetadataExpanded.publishedYear; -// } -// } -// } - -// extension BookExpandedExtension on BookExpanded { -// BookChapter findChapterAtTime(Duration position) { -// return chapters.firstWhere( -// (element) { -// return element.start <= position && element.end >= position + offset; -// }, -// orElse: () => chapters.first, -// ); -// } - -// AudioTrack findTrackAtTime(Duration position) { -// return tracks.firstWhere( -// (element) { -// return element.startOffset <= position && -// element.startOffset + element.duration >= position + offset; -// }, -// orElse: () => tracks.first, -// ); -// } - -// int findTrackIndexAtTime(Duration position) { -// return tracks.indexWhere((element) { -// return element.startOffset <= position && -// element.startOffset + element.duration >= position + offset; -// }); -// } - -// Duration getTrackStartOffset(int index) { -// return tracks[index].startOffset; -// } -// } diff --git a/lib/features/player/core/init.dart b/lib/features/player/core/init.dart index 014e7ef..bac1d2c 100644 --- a/lib/features/player/core/init.dart +++ b/lib/features/player/core/init.dart @@ -1,20 +1,19 @@ import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:media_kit/media_kit.dart'; import 'package:vaani/features/player/core/abs_audio_handler.dart' as core; -import 'package:vaani/features/player/providers/abs_provider.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; import 'package:vaani/globals.dart'; -Future configurePlayer(ProviderContainer container) async { +/// 音频播放器 配置 +Future configurePlayer(AbsAudioPlayer player) async { // for playing audio on windows, linux - MediaKit.ensureInitialized(); + // for configuring how this app will interact with other audio apps final session = await AudioSession.instance; await session.configure(const AudioSessionConfiguration.speech()); await AudioService.init( - builder: () => core.AbsAudioHandler(container.read(absAudioPlayerProvider)), + builder: () => core.AbsAudioHandler(player), config: const AudioServiceConfig( androidNotificationChannelId: 'dr.blank.vaani.channel.audio', androidNotificationChannelName: 'ABSPlayback', diff --git a/lib/features/player/core/player_status.dart b/lib/features/player/core/player_status.dart deleted file mode 100644 index e911cff..0000000 --- a/lib/features/player/core/player_status.dart +++ /dev/null @@ -1,58 +0,0 @@ -enum PlayStatus { stopped, playing, paused, hidden, loading, completed } - -class PlayerStatus { - PlayStatus playStatus; - String itemId; - bool quite; - - PlayerStatus({ - this.playStatus = PlayStatus.hidden, - this.itemId = '', - this.quite = false, - }) { - // addListener(_onStatusChanged); - } - bool isPlaying({String? itemId}) { - if (itemId != null && this.itemId.isNotEmpty) { - return playStatus == PlayStatus.playing && this.itemId == itemId; - } else { - return playStatus == PlayStatus.playing; - } - } - - bool isPaused({String? itemId}) { - if (itemId != null && this.itemId.isNotEmpty) { - return playStatus == PlayStatus.paused && this.itemId == itemId; - } else { - return playStatus == PlayStatus.paused; - } - } - - bool isStopped({String? itemId}) { - if (itemId != null && this.itemId.isNotEmpty) { - return playStatus == PlayStatus.stopped && this.itemId == itemId; - } else { - return playStatus == PlayStatus.stopped; - } - } - - bool isLoading(String? itemId) { - if (itemId != null && this.itemId.isNotEmpty) { - return playStatus == PlayStatus.loading && this.itemId == itemId; - } else { - return playStatus == PlayStatus.loading; - } - } - - PlayerStatus copyWith({ - PlayStatus? playStatus, - String? itemId, - bool? quite, - }) { - return PlayerStatus( - playStatus: playStatus ?? this.playStatus, - itemId: itemId ?? this.itemId, - quite: quite ?? this.quite, - ); - } -} diff --git a/lib/features/player/providers/abs_provider.dart b/lib/features/player/providers/abs_provider.dart index b0f78d4..e6c648a 100644 --- a/lib/features/player/providers/abs_provider.dart +++ b/lib/features/player/providers/abs_provider.dart @@ -2,18 +2,19 @@ import 'package:hooks_riverpod/hooks_riverpod.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/shared/audio_player.dart' as core; -import 'package:vaani/shared/audio_player_mpv.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart' as core; +import 'package:vaani/features/player/core/abs_audio_player_platform.dart'; part 'abs_provider.g.dart'; // final _logger = Logger('AbsPlayerProvider'); - +/// 音频播放器 riverpod状态 @Riverpod(keepAlive: true) -class AbsAudioPlayer extends _$AbsAudioPlayer { +class AudioPlayer extends _$AudioPlayer { @override core.AbsAudioPlayer build() { - final player = AbsMpvAudioPlayer(); + // final player = AbsMpvAudioPlayer(); + final player = AbsPlatformAudioPlayer(); return player; } @@ -40,7 +41,7 @@ class AbsAudioPlayer extends _$AbsAudioPlayer { class PlayerState extends _$PlayerState { @override core.AbsPlayerState build() { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); player.playerStateStream.listen((playerState) { if (playerState != state) { state = playerState; @@ -50,7 +51,7 @@ class PlayerState extends _$PlayerState { } bool isLoading(String itemId) { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); return player.book?.libraryItemId == itemId && !state.playing && state.processingState == core.AbsProcessingState.loading; @@ -65,7 +66,7 @@ class PlayerState extends _$PlayerState { class CurrentBook extends _$CurrentBook { @override api.BookExpanded? build() { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); player.bookStream.listen((book) { if (book != state) { state = book; @@ -77,7 +78,7 @@ class CurrentBook extends _$CurrentBook { @riverpod bool isPlayerActive(Ref ref) { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); player.bookStream.listen((book) { ref.invalidateSelf(); }); @@ -88,7 +89,7 @@ bool isPlayerActive(Ref ref) { class CurrentChapter extends _$CurrentChapter { @override api.BookChapter? build() { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); player.chapterStream.listen((chapter) { if (chapter != state) { state = chapter; @@ -100,7 +101,7 @@ class CurrentChapter extends _$CurrentChapter { @riverpod Stream positionChapter(Ref ref) { - return ref.read(absAudioPlayerProvider).positionInChapterStream; + return ref.read(audioPlayerProvider).positionInChapterStream; } @riverpod diff --git a/lib/features/player/providers/abs_provider.g.dart b/lib/features/player/providers/abs_provider.g.dart index 5ade537..7a07880 100644 --- a/lib/features/player/providers/abs_provider.g.dart +++ b/lib/features/player/providers/abs_provider.g.dart @@ -6,7 +6,7 @@ part of 'abs_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$isPlayerActiveHash() => r'52fc689deeba4d21a33a73290d297f128324ae99'; +String _$isPlayerActiveHash() => r'71a24418ecf6c1a2d8160b0d0c8fc523d5679e76'; /// See also [isPlayerActive]. @ProviderFor(isPlayerActive) @@ -23,7 +23,7 @@ final isPlayerActiveProvider = AutoDisposeProvider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef IsPlayerActiveRef = AutoDisposeProviderRef; -String _$positionChapterHash() => r'68f7ca4df2ac6f6f78a645d98e2dbfaf2ffe46bf'; +String _$positionChapterHash() => r'750b8e2f2c7217b59c3d77ed66dd20798f8787fa'; /// See also [positionChapter]. @ProviderFor(positionChapter) @@ -58,23 +58,24 @@ final currentChaptersProvider = @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef CurrentChaptersRef = AutoDisposeProviderRef>; -String _$absAudioPlayerHash() => r'04636b3275f16747eeeb008c8b4dda4e8a1f8ed2'; +String _$audioPlayerHash() => r'26387ece7f0d0c0cc21dc7641853e643866726f6'; -/// See also [AbsAudioPlayer]. -@ProviderFor(AbsAudioPlayer) -final absAudioPlayerProvider = - NotifierProvider.internal( - AbsAudioPlayer.new, - name: r'absAudioPlayerProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$absAudioPlayerHash, +/// 音频播放器 riverpod状态 +/// +/// Copied from [AudioPlayer]. +@ProviderFor(AudioPlayer) +final audioPlayerProvider = + NotifierProvider.internal( + AudioPlayer.new, + name: r'audioPlayerProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$audioPlayerHash, dependencies: null, allTransitiveDependencies: null, ); -typedef _$AbsAudioPlayer = Notifier; -String _$playerStateHash() => r'6635671077b077f48dad173c4393462921de56f8'; +typedef _$AudioPlayer = Notifier; +String _$playerStateHash() => r'7e238aea9306cdfb952b546c76d1e894888c586f'; /// See also [PlayerState]. @ProviderFor(PlayerState) @@ -89,7 +90,7 @@ final playerStateProvider = ); typedef _$PlayerState = AutoDisposeNotifier; -String _$currentBookHash() => r'40c24ad45aab37afc32e8e8383d6abbe19b714bc'; +String _$currentBookHash() => r'3684426dfde84e49dc2021e8444a2a3026082942'; /// See also [CurrentBook]. @ProviderFor(CurrentBook) @@ -104,7 +105,7 @@ final currentBookProvider = ); typedef _$CurrentBook = AutoDisposeNotifier; -String _$currentChapterHash() => r'89868a72b106e0916883ee92bf3d18650288c586'; +String _$currentChapterHash() => r'28ac34fa83cbd6acf745e06b91b9ce36733fdbe5'; /// See also [CurrentChapter]. @ProviderFor(CurrentChapter) diff --git a/lib/features/player/providers/audiobook_player.dart b/lib/features/player/providers/audiobook_player.dart deleted file mode 100644 index 77be45f..0000000 --- a/lib/features/player/providers/audiobook_player.dart +++ /dev/null @@ -1,108 +0,0 @@ -// import 'package:audio_service/audio_service.dart'; -// import 'package:audio_session/audio_session.dart'; -// // import 'package:just_audio_media_kit/just_audio_media_kit.dart'; -// import 'package:riverpod/riverpod.dart'; -// import 'package:riverpod_annotation/riverpod_annotation.dart'; -// import 'package:vaani/features/player/core/audiobook_player.dart'; - -// part 'audiobook_player.g.dart'; - -// @Riverpod(keepAlive: true) -// Future audioHandlerInit(Ref ref) async { -// // for playing audio on windows, linux -// // JustAudioMediaKit.ensureInitialized(); - -// // for configuring how this app will interact with other audio apps -// final session = await AudioSession.instance; -// await session.configure(const AudioSessionConfiguration.speech()); - -// final audioService = AudioService.init( -// builder: () => AbsAudioHandler(ref), -// config: const AudioServiceConfig( -// androidNotificationChannelId: 'dr.blank.vaani.channel.audio', -// androidNotificationChannelName: 'ABSPlayback', -// androidNotificationChannelDescription: -// 'Needed to control audio from lock screen', -// androidNotificationOngoing: false, -// androidStopForegroundOnPause: false, -// androidNotificationIcon: 'drawable/ic_stat_logo', -// preloadArtwork: true, -// // fastForwardInterval: Duration(seconds: 20), -// // rewindInterval: Duration(seconds: 20), -// ), -// ); -// return audioService; -// } - -// @Riverpod(keepAlive: true) -// class Player extends _$Player { -// @override -// AbsAudioHandler build() { -// return ref.watch(audioHandlerInitProvider).requireValue; -// } -// } - -// // @Riverpod(keepAlive: true) -// // class Session extends _$Session { -// // @override -// // core.PlaybackSessionExpanded? build() { -// // return null; -// // } - -// // Future load(String id, String? episodeId) async { -// // final audioService = ref.read(playerProvider); -// // await audioService.pause(); -// // ref.read(playerStatusProvider.notifier).setLoading(id); -// // final api = ref.read(authenticatedApiProvider); -// // final playBack = await ref.watch(playBackSessionProvider(id).future); -// // if (playBack == null) { -// // return; -// // } -// // state = playBack.asExpanded; -// // final downloadManager = ref.read(simpleDownloadManagerProvider); -// // final libItem = -// // await ref.read(libraryItemProvider(state!.libraryItemId).future); -// // final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem); - -// // var bookPlayerSettings = -// // ref.read(bookSettingsProvider(state!.libraryItemId)).playerSettings; -// // var appPlayerSettings = ref.read(appSettingsProvider).playerSettings; - -// // var configurePlayerForEveryBook = -// // appPlayerSettings.configurePlayerForEveryBook; - -// // await Future.wait([ -// // audioService.setSourceAudiobook( -// // state!.asExpanded, -// // baseUrl: api.baseUrl, -// // token: api.token!, -// // downloadedUris: downloadedUris, -// // ), -// // // set the volume -// // audioService.setVolume( -// // configurePlayerForEveryBook -// // ? bookPlayerSettings.preferredDefaultVolume ?? -// // appPlayerSettings.preferredDefaultVolume -// // : appPlayerSettings.preferredDefaultVolume, -// // ), -// // // set the speed -// // audioService.setSpeed( -// // configurePlayerForEveryBook -// // ? bookPlayerSettings.preferredDefaultSpeed ?? -// // appPlayerSettings.preferredDefaultSpeed -// // : appPlayerSettings.preferredDefaultSpeed, -// // ), -// // ]); -// // } -// // } - -// class PlaybackSyncError implements Exception { -// String message; - -// PlaybackSyncError([this.message = 'Error syncing playback']); - -// @override -// String toString() { -// return 'PlaybackSyncError: $message'; -// } -// } diff --git a/lib/features/player/providers/currently_playing_provider.dart b/lib/features/player/providers/currently_playing_provider.dart deleted file mode 100644 index 66ae515..0000000 --- a/lib/features/player/providers/currently_playing_provider.dart +++ /dev/null @@ -1,80 +0,0 @@ -// import 'package:hooks_riverpod/hooks_riverpod.dart'; -// import 'package:riverpod_annotation/riverpod_annotation.dart'; -// import 'package:shelfsdk/audiobookshelf_api.dart' as core; -// 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/providers/audiobook_player.dart'; -// import 'package:vaani/features/settings/app_settings_provider.dart'; -// import 'package:vaani/globals.dart'; - -// part 'currently_playing_provider.g.dart'; - -// @riverpod -// class CurrentBook extends _$CurrentBook { -// @override -// core.BookExpanded? build() { -// return null; -// } - -// Future update(core.BookExpanded book, Duration? currentTime) async { -// final audioService = ref.read(playerProvider); -// if (state == book) { -// appLogger.info('Book was already set'); -// if (audioService.player.playing) { -// appLogger.info('Pausing the book'); -// await audioService.pause(); -// return; -// } else { -// await audioService.play(); -// } -// } -// state = book; -// final api = ref.read(authenticatedApiProvider); -// final downloadManager = ref.read(simpleDownloadManagerProvider); -// final libItem = -// await ref.read(libraryItemProvider(state!.libraryItemId).future); -// final downloadedUris = await downloadManager.getDownloadedFilesUri(libItem); - -// var bookPlayerSettings = -// ref.read(bookSettingsProvider(state!.libraryItemId)).playerSettings; -// var appPlayerSettings = ref.read(appSettingsProvider).playerSettings; - -// var configurePlayerForEveryBook = -// appPlayerSettings.configurePlayerForEveryBook; -// audioService.setSourceAudiobook( -// state!, -// baseUrl: api.baseUrl, -// token: api.token!, -// initialPosition: currentTime, -// downloadedUris: downloadedUris, -// volume: configurePlayerForEveryBook -// ? bookPlayerSettings.preferredDefaultVolume ?? -// appPlayerSettings.preferredDefaultVolume -// : appPlayerSettings.preferredDefaultVolume, -// speed: configurePlayerForEveryBook -// ? bookPlayerSettings.preferredDefaultSpeed ?? -// appPlayerSettings.preferredDefaultSpeed -// : appPlayerSettings.preferredDefaultSpeed, -// ); -// } -// } - -// @riverpod -// class CurrentChapter extends _$CurrentChapter { -// @override -// core.BookChapter? build() { -// final player = ref.watch(playerProvider); -// player.chapterStream.distinct().listen((chapter) { -// update(chapter); -// }); -// return player.currentChapter; -// } - -// void update(core.BookChapter? chapter) { -// if (state != chapter) { -// state = chapter; -// } -// } -// } diff --git a/lib/features/player/providers/player_status_provider.dart b/lib/features/player/providers/player_status_provider.dart deleted file mode 100644 index f193e53..0000000 --- a/lib/features/player/providers/player_status_provider.dart +++ /dev/null @@ -1,40 +0,0 @@ -// import 'package:riverpod_annotation/riverpod_annotation.dart'; -// import 'package:vaani/features/player/core/player_status.dart' as core; - -// part 'player_status_provider.g.dart'; - -// @Riverpod(keepAlive: true) -// class PlayerStatus extends _$PlayerStatus { -// @override -// core.PlayerStatus build() { -// return core.PlayerStatus(); -// } - -// void setPlayStatus(core.PlayStatus playStatus) { -// state = state.copyWith(playStatus: playStatus); -// } - -// void setPlayStatusQuietly(core.PlayStatus playStatus) { -// // state.copyWith(quite: true); -// setPlayStatus(playStatus); -// // state.copyWith(quite: false); -// } - -// // 校验原值, 不相同则更新 -// void setPlayStatusVerify(core.PlayStatus playStatus) { -// if (state.playStatus != playStatus) { -// setPlayStatus(playStatus); -// } -// } - -// void setLoading(String itemId) { -// state = state.copyWith( -// playStatus: core.PlayStatus.loading, -// itemId: itemId, -// ); -// } - -// void setHidden() { -// state = state.copyWith(playStatus: core.PlayStatus.hidden); -// } -// } diff --git a/lib/features/player/view/mini_player_bottom_padding.dart b/lib/features/player/view/mini_player_bottom_padding.dart index eaf7a12..f59c213 100644 --- a/lib/features/player/view/mini_player_bottom_padding.dart +++ b/lib/features/player/view/mini_player_bottom_padding.dart @@ -9,7 +9,7 @@ class MiniPlayerBottomPadding extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return AnimatedSize( duration: const Duration(milliseconds: 200), - child: ref.watch(absAudioPlayerProvider).playing + child: ref.watch(audioPlayerProvider).playing ? const SizedBox(height: playerMinHeight + 8) : const SizedBox.shrink(), ); diff --git a/lib/features/player/view/player_expanded_desktop.dart b/lib/features/player/view/player_expanded_desktop.dart index 3b4fa27..6e3e081 100644 --- a/lib/features/player/view/player_expanded_desktop.dart +++ b/lib/features/player/view/player_expanded_desktop.dart @@ -12,6 +12,7 @@ import 'package:vaani/features/player/view/player_minimized.dart'; import 'package:vaani/features/player/view/widgets/audiobook_player_seek_button.dart'; import 'package:vaani/features/player/view/widgets/audiobook_player_seek_chapter_button.dart'; import 'package:vaani/features/player/view/widgets/player_player_pause_button.dart'; +import 'package:vaani/features/player/view/widgets/player_progress_bar.dart'; import 'package:vaani/features/player/view/widgets/player_speed_adjust_button.dart'; import 'package:vaani/features/skip_start_end/view/skip_start_end_button.dart'; import 'package:vaani/features/sleep_timer/view/sleep_timer_button.dart'; @@ -64,35 +65,18 @@ class PlayerExpandedDesktop extends HookConsumerWidget { // add a shadow to the image elevation hovering effect child: PlayerExpandedImage(imageSize), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // previous chapter - const AudiobookPlayerSeekChapterButton( - isForward: false), - // buttonSkipBackwards - const AudiobookPlayerSeekButton(isForward: false), - AudiobookPlayerPlayPauseButton(), - // // buttonSkipForwards - const AudiobookPlayerSeekButton(isForward: true), - // // next chapter - const AudiobookPlayerSeekChapterButton( - isForward: true), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - // speed control - const PlayerSpeedAdjustButton(), - const Spacer(), - // sleep timer - const SleepTimerButton(), - const Spacer(), - // 跳过片头片尾 - SkipChapterStartEndButton(), - ], + _buildControls(imageSize), + SizedBox( + width: imageSize, + child: Padding( + padding: EdgeInsets.only( + left: AppElementSizes.paddingRegular, + right: AppElementSizes.paddingRegular, + ), + child: const AudiobookChapterProgressBar(), + ), ), + _buildSettings(imageSize), ], ), ), @@ -130,10 +114,49 @@ class PlayerExpandedDesktop extends HookConsumerWidget { ), ), ), - Hero(tag: 'player_hero', child: const PlayerMinimized()), + Hero(tag: 'player_hero', child: const PlayerMinimizedControls()), ], ); } + + Widget _buildControls(double width) { + return SizedBox( + width: width, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // previous chapter + const AudiobookPlayerSeekChapterButton(isForward: false), + // buttonSkipBackwards + const AudiobookPlayerSeekButton(isForward: false), + AudiobookPlayerPlayPauseButton(), + // // buttonSkipForwards + const AudiobookPlayerSeekButton(isForward: true), + // // next chapter + const AudiobookPlayerSeekChapterButton(isForward: true), + ], + ), + ); + } + + Widget _buildSettings(double width) { + return SizedBox( + width: width, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // speed control + const PlayerSpeedAdjustButton(), + const Spacer(), + // sleep timer + const SleepTimerButton(), + const Spacer(), + // 跳过片头片尾 + SkipChapterStartEndButton(), + ], + ), + ); + } } class ChapterSelection extends HookConsumerWidget { @@ -200,7 +223,7 @@ class ChapterSelection extends HookConsumerWidget { selected: isCurrent, // key: isCurrent ? chapterKey : null, onTap: () { - ref.read(absAudioPlayerProvider).switchChapter(chapter.id); + ref.read(audioPlayerProvider).switchChapter(chapter.id); }, ); }, diff --git a/lib/features/player/view/player_minimized.dart b/lib/features/player/view/player_minimized.dart index 52ae52b..6ab560d 100644 --- a/lib/features/player/view/player_minimized.dart +++ b/lib/features/player/view/player_minimized.dart @@ -22,105 +22,32 @@ class PlayerMinimized extends HookConsumerWidget { if (currentBook == null) { return SizedBox.shrink(); } - final currentChapter = ref.watch(currentChapterProvider); - - return PlayerMinimizedFramework( - children: [ - // image - Padding( - padding: EdgeInsets.all(AppElementSizes.paddingSmall), - child: GestureDetector( - onTap: () { - // navigate to item page - context.pushNamed( - Routes.libraryItem.name, - pathParameters: { - Routes.libraryItem.pathParamName!: currentBook.libraryItemId, - }, - ); - }, - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: playerMinimizedHeight, - ), - child: BookCoverWidget(), - ), - ), + return GestureDetector( + child: Container( + height: playerMinimizedHeight, + color: Theme.of(context).colorScheme.surface, + child: Stack( + alignment: Alignment.topCenter, + children: [ + Hero(tag: 'player_hero', child: const PlayerMinimizedControls()), + PlayerMinimizedProgress(), + ], ), - // author and title of the book - Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: AppElementSizes.paddingRegular, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - // AutoScrollText( - Text( - '${currentBook.metadata.title ?? ''} - ${currentChapter?.title ?? ''}', - maxLines: 1, overflow: TextOverflow.ellipsis, - // velocity: - // const Velocity(pixelsPerSecond: Offset(16, 0)), - style: Theme.of(context).textTheme.bodyLarge, - ), - Text( - currentBook.metadata.asBookMetadataExpanded.authorName ?? '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context) - .colorScheme - .onSurface - .withValues(alpha: 0.7), - ), - ), - ], - ), - ), - ), - - // rewind button - Padding( - padding: const EdgeInsets.only(left: 8), - child: IconButton( - icon: const Icon( - Icons.replay_30, - size: AppElementSizes.iconSizeSmall, - ), - onPressed: () {}, - ), - ), - - // play/pause button - Padding( - padding: const EdgeInsets.only(right: 8), - child: AudiobookPlayerPlayPauseButton(), - ), - ], + ), ); } } -class PlayerMinimizedFramework extends HookConsumerWidget { - final List children; - - const PlayerMinimizedFramework({super.key, required this.children}); +class PlayerMinimizedControls extends HookConsumerWidget { + const PlayerMinimizedControls({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - // final player = ref.watch(playerProvider); + final currentBook = ref.watch(currentBookProvider); final currentChapter = ref.watch(currentChapterProvider); - final progress = - // useStream(player.positionStreamInChapter, initialData: Duration.zero); - useStream( - ref.read(absAudioPlayerProvider).positionInChapterStream, - initialData: Duration.zero, - ); return GestureDetector( + behavior: HitTestBehavior.opaque, onTap: () { if (GoRouterState.of(context).topRoute?.name != Routes.player.name) { context.pushNamed(Routes.player.name); @@ -128,26 +55,109 @@ class PlayerMinimizedFramework extends HookConsumerWidget { context.pop(); } }, - child: Container( - height: playerMinimizedHeight, - color: Theme.of(context).colorScheme.surface, - child: Stack( - alignment: Alignment.topCenter, - children: [ - Row( - children: children, - ), - SizedBox( - height: AppElementSizes.barHeight, - child: LinearProgressIndicator( - value: (progress.data ?? Duration.zero).inSeconds / - (currentChapter?.duration.inSeconds ?? 1), - // color: Theme.of(context).colorScheme.onPrimaryContainer, - // backgroundColor: Theme.of(context).colorScheme.primaryContainer, + child: Row( + children: [ + // image + Padding( + padding: EdgeInsets.all(AppElementSizes.paddingSmall), + child: GestureDetector( + onTap: () { + // navigate to item page + if (currentBook != null) { + context.pushNamed( + Routes.libraryItem.name, + pathParameters: { + Routes.libraryItem.pathParamName!: + currentBook.libraryItemId, + }, + ); + } + }, + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: playerMinimizedHeight, + ), + child: BookCoverWidget(), ), ), - ], - ), + ), + // author and title of the book + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: AppElementSizes.paddingRegular, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + // AutoScrollText( + Text( + '${currentBook?.metadata.title ?? ''} - ${currentChapter?.title ?? ''}', + maxLines: 1, overflow: TextOverflow.ellipsis, + // velocity: + // const Velocity(pixelsPerSecond: Offset(16, 0)), + style: Theme.of(context).textTheme.bodyLarge, + ), + Text( + currentBook?.metadata.asBookMetadataExpanded.authorName ?? + '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context) + .colorScheme + .onSurface + .withValues(alpha: 0.7), + ), + ), + ], + ), + ), + ), + + // rewind button + Padding( + padding: const EdgeInsets.only(left: 8), + child: IconButton( + icon: const Icon( + Icons.replay_30, + size: AppElementSizes.iconSizeSmall, + ), + onPressed: () {}, + ), + ), + + // play/pause button + Padding( + padding: const EdgeInsets.only(right: 8), + child: AudiobookPlayerPlayPauseButton(), + ), + ], + ), + ); + } +} + +class PlayerMinimizedProgress extends HookConsumerWidget { + const PlayerMinimizedProgress({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final currentChapter = ref.watch(currentChapterProvider); + + final progress = useStream( + ref.read(audioPlayerProvider).positionInChapterStream, + initialData: Duration.zero, + ); + return SizedBox( + height: AppElementSizes.barHeight, + child: LinearProgressIndicator( + value: (progress.data ?? Duration.zero).inSeconds / + (currentChapter?.duration.inSeconds ?? 1), + // color: Theme.of(context).colorScheme.onPrimaryContainer, + // backgroundColor: Theme.of(context).colorScheme.primaryContainer, ), ); } diff --git a/lib/features/player/view/widgets/audiobook_player_seek_button.dart b/lib/features/player/view/widgets/audiobook_player_seek_button.dart index db5f5d2..68b6745 100644 --- a/lib/features/player/view/widgets/audiobook_player_seek_button.dart +++ b/lib/features/player/view/widgets/audiobook_player_seek_button.dart @@ -14,7 +14,7 @@ class AudiobookPlayerSeekButton extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); return IconButton( icon: Icon( isForward ? Icons.forward_30 : Icons.replay_30, diff --git a/lib/features/player/view/widgets/audiobook_player_seek_chapter_button.dart b/lib/features/player/view/widgets/audiobook_player_seek_chapter_button.dart index c8450c2..ca098ab 100644 --- a/lib/features/player/view/widgets/audiobook_player_seek_chapter_button.dart +++ b/lib/features/player/view/widgets/audiobook_player_seek_chapter_button.dart @@ -20,7 +20,7 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget { size: AppElementSizes.iconSizeSmall, ), onPressed: () { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); final book = ref.read(currentBookProvider); if (book == null) { return; diff --git a/lib/features/player/view/widgets/chapter_selection_button.dart b/lib/features/player/view/widgets/chapter_selection_button.dart index 3e1a975..e3ed021 100644 --- a/lib/features/player/view/widgets/chapter_selection_button.dart +++ b/lib/features/player/view/widgets/chapter_selection_button.dart @@ -116,7 +116,7 @@ class ChapterSelectionModal extends HookConsumerWidget { onTap: () { Navigator.of(context).pop(); ref - .read(absAudioPlayerProvider) + .read(audioPlayerProvider) .switchChapter(chapter.id); }, ); diff --git a/lib/features/player/view/widgets/player_player_pause_button.dart b/lib/features/player/view/widgets/player_player_pause_button.dart index 03e4405..0a3ff88 100644 --- a/lib/features/player/view/widgets/player_player_pause_button.dart +++ b/lib/features/player/view/widgets/player_player_pause_button.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:vaani/features/player/providers/abs_provider.dart' hide PlayerState; -import 'package:vaani/shared/audio_player.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; class AudiobookPlayerPlayPauseButton extends HookConsumerWidget { const AudiobookPlayerPlayPauseButton({ @@ -35,7 +35,7 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget { } void _actionButtonPressed(AbsPlayerState playerState, WidgetRef ref) async { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); if (playerState.playing) { await player.pause(); } else { diff --git a/lib/features/player/view/widgets/player_progress_bar.dart b/lib/features/player/view/widgets/player_progress_bar.dart index 1e5b254..e71dea1 100644 --- a/lib/features/player/view/widgets/player_progress_bar.dart +++ b/lib/features/player/view/widgets/player_progress_bar.dart @@ -12,7 +12,7 @@ class AudiobookChapterProgressBar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final player = ref.watch(absAudioPlayerProvider); + final player = ref.watch(audioPlayerProvider); final currentChapter = ref.watch(currentChapterProvider); final position = useStream( player.positionInBookStream, @@ -63,7 +63,7 @@ class AudiobookProgressBar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); final position = useStream( player.positionInBookStream, initialData: const Duration(seconds: 0), diff --git a/lib/features/player/view/widgets/player_speed_adjust_button.dart b/lib/features/player/view/widgets/player_speed_adjust_button.dart index c3f0618..c394631 100644 --- a/lib/features/player/view/widgets/player_speed_adjust_button.dart +++ b/lib/features/player/view/widgets/player_speed_adjust_button.dart @@ -16,7 +16,7 @@ class PlayerSpeedAdjustButton extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); final book = ref.read(currentBookProvider); final bookId = book?.libraryItemId ?? '_'; final bookSettings = ref.watch(bookSettingsProvider(bookId)); diff --git a/lib/features/player/view/widgets/speed_selector.dart b/lib/features/player/view/widgets/speed_selector.dart index 12a6966..085d642 100644 --- a/lib/features/player/view/widgets/speed_selector.dart +++ b/lib/features/player/view/widgets/speed_selector.dart @@ -22,7 +22,7 @@ class SpeedSelector extends HookConsumerWidget { final appSettings = ref.watch(appSettingsProvider); final playerSettings = appSettings.playerSettings; final speeds = playerSettings.speedOptions; - final currentSpeed = ref.watch(absAudioPlayerProvider).speed; + final currentSpeed = ref.watch(audioPlayerProvider).speed; final speedState = useState(currentSpeed); // hook the onSpeedSelected function to the state diff --git a/lib/features/shake_detector/shake_detector_provider.dart b/lib/features/shake_detector/shake_detector_provider.dart index 458ff17..a1069e2 100644 --- a/lib/features/shake_detector/shake_detector_provider.dart +++ b/lib/features/shake_detector/shake_detector_provider.dart @@ -2,13 +2,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:just_audio/just_audio.dart'; import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:vaani/features/player/providers/abs_provider.dart'; +import 'package:vaani/features/player/providers/abs_provider.dart' + hide AudioPlayer; import 'package:vaani/features/settings/app_settings_provider.dart' show appSettingsProvider; import 'package:vaani/features/settings/models/app_settings.dart'; import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart' show sleepTimerProvider; -import 'package:vaani/shared/audio_player.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; import 'package:vibration/vibration.dart'; import 'shake_detector.dart' as core; @@ -32,7 +33,7 @@ class ShakeDetector extends _$ShakeDetector { } // if no book is loaded, shake detection should not be enabled - final player = ref.watch(absAudioPlayerProvider); + final player = ref.watch(audioPlayerProvider); player.playerStateStream.listen((event) { if (event.processingState == AbsProcessingState.idle && wasPlayerLoaded) { _logger.config('Player is now not loaded, invalidating'); @@ -88,7 +89,7 @@ class ShakeDetector extends _$ShakeDetector { ShakeAction shakeAction, { required Ref ref, }) { - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); if (player.book == null && shakeAction.isPlaybackManagementEnabled) { _logger.warning('No book is loaded'); return false; diff --git a/lib/features/shake_detector/shake_detector_provider.g.dart b/lib/features/shake_detector/shake_detector_provider.g.dart index 76a2526..de924df 100644 --- a/lib/features/shake_detector/shake_detector_provider.g.dart +++ b/lib/features/shake_detector/shake_detector_provider.g.dart @@ -6,7 +6,7 @@ part of 'shake_detector_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$shakeDetectorHash() => r'ea00d8c830ef67f968bee25e0f924a35dc3e4cbf'; +String _$shakeDetectorHash() => r'c75e0308478cd70ef4b5cdd5f72cf706d597900c'; /// See also [ShakeDetector]. @ProviderFor(ShakeDetector) diff --git a/lib/features/skip_start_end/core/skip_start_end.dart b/lib/features/skip_start_end/core/skip_start_end.dart index 1a3a75d..7cc7d5c 100644 --- a/lib/features/skip_start_end/core/skip_start_end.dart +++ b/lib/features/skip_start_end/core/skip_start_end.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:vaani/shared/audio_player.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; import 'package:vaani/shared/extensions/chapter.dart'; import 'package:vaani/shared/utils/throttler.dart'; diff --git a/lib/features/skip_start_end/providers/skip_start_end_provider.dart b/lib/features/skip_start_end/providers/skip_start_end_provider.dart index 18dba12..c42df87 100644 --- a/lib/features/skip_start_end/providers/skip_start_end_provider.dart +++ b/lib/features/skip_start_end/providers/skip_start_end_provider.dart @@ -15,7 +15,7 @@ class SkipStartEnd extends _$SkipStartEnd { return null; } - final player = ref.read(absAudioPlayerProvider); + final player = ref.read(audioPlayerProvider); final bookSettings = ref.watch(bookSettingsProvider(bookId)); final start = bookSettings.playerSettings.skipChapterStart; final end = bookSettings.playerSettings.skipChapterEnd; diff --git a/lib/features/skip_start_end/providers/skip_start_end_provider.g.dart b/lib/features/skip_start_end/providers/skip_start_end_provider.g.dart index 0827087..b1aa220 100644 --- a/lib/features/skip_start_end/providers/skip_start_end_provider.g.dart +++ b/lib/features/skip_start_end/providers/skip_start_end_provider.g.dart @@ -6,7 +6,7 @@ part of 'skip_start_end_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$skipStartEndHash() => r'b55dff90ed4ba467e9320d6bc081721336975bdb'; +String _$skipStartEndHash() => r'f14f84be713bdaad463fcf790510cddeb2be7709'; /// See also [SkipStartEnd]. @ProviderFor(SkipStartEnd) diff --git a/lib/features/sleep_timer/core/sleep_timer.dart b/lib/features/sleep_timer/core/sleep_timer.dart index e746002..096ca55 100644 --- a/lib/features/sleep_timer/core/sleep_timer.dart +++ b/lib/features/sleep_timer/core/sleep_timer.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:logging/logging.dart'; -import 'package:vaani/shared/audio_player.dart'; +import 'package:vaani/features/player/core/abs_audio_player.dart'; /// this timer pauses the music player after a certain duration /// diff --git a/lib/features/sleep_timer/providers/sleep_timer_provider.dart b/lib/features/sleep_timer/providers/sleep_timer_provider.dart index e931478..8247067 100644 --- a/lib/features/sleep_timer/providers/sleep_timer_provider.dart +++ b/lib/features/sleep_timer/providers/sleep_timer_provider.dart @@ -26,7 +26,7 @@ class SleepTimer extends _$SleepTimer { var sleepTimer = core.SleepTimer( duration: sleepTimerSettings.defaultDuration, - player: ref.watch(absAudioPlayerProvider), + player: ref.watch(audioPlayerProvider), ); ref.onDispose(sleepTimer.dispose); return sleepTimer; @@ -45,7 +45,7 @@ class SleepTimer extends _$SleepTimer { } else { final timer = core.SleepTimer( duration: resultingDuration, - player: ref.watch(absAudioPlayerProvider), + player: ref.watch(audioPlayerProvider), ); ref.onDispose(timer.dispose); state = timer; diff --git a/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart b/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart index 2a52aee..20d073e 100644 --- a/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart +++ b/lib/features/sleep_timer/providers/sleep_timer_provider.g.dart @@ -6,7 +6,7 @@ part of 'sleep_timer_provider.dart'; // RiverpodGenerator // ************************************************************************** -String _$sleepTimerHash() => r'8d91e2f7563320985ff5693b539288a93ff0d243'; +String _$sleepTimerHash() => r'89ff64cd768deea9ed4ab103ddde918b3f96d705'; /// See also [SleepTimer]. @ProviderFor(SleepTimer) diff --git a/lib/framework.dart b/lib/framework.dart index 0bab5b4..adb88d0 100644 --- a/lib/framework.dart +++ b/lib/framework.dart @@ -18,11 +18,11 @@ class Framework extends ConsumerWidget { // Eagerly initialize providers by watching them. // By using "watch", the provider will stay alive and not be disposed. try { - // ref.watch(simpleDownloadManagerProvider); - // if (Helper.isAndroid()) ref.watch(shakeDetectorProvider); - // ref.watch(sleepTimerProvider); + ref.watch(simpleDownloadManagerProvider); + if (Helper.isAndroid()) ref.watch(shakeDetectorProvider); + ref.watch(sleepTimerProvider); // ref.watch(skipStartEndProvider); - // ref.watch(playbackReporterProvider); + ref.watch(playbackReporterProvider); } catch (e) { debugPrintStack(stackTrace: StackTrace.current, label: e.toString()); appLogger.severe(e.toString()); diff --git a/lib/main.dart b/lib/main.dart index f6e103c..2d1256c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -38,7 +38,8 @@ void main() async { // initialize audio player // await configurePlayer(); - await configurePlayer(container); + final player = container.read(audioPlayerProvider); + await configurePlayer(player); // run the app runApp( UncontrolledProviderScope( diff --git a/lib/pages/player_page.dart b/lib/pages/player_page.dart index bb4b2b6..16b8b23 100644 --- a/lib/pages/player_page.dart +++ b/lib/pages/player_page.dart @@ -35,8 +35,7 @@ class PlayerPage extends HookConsumerWidget { ), ], ), - // body: isVertical ? PlayerExpanded() : PlayerExpandedDesktop(), - body: PlayerExpanded(), + body: isVertical ? PlayerExpanded() : PlayerExpandedDesktop(), ); } } diff --git a/lib/router/router.dart b/lib/router/router.dart index 188ec72..be1bad8 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -4,7 +4,6 @@ import 'package:vaani/features/downloads/view/downloads_page.dart'; import 'package:vaani/features/explore/view/explore_page.dart'; import 'package:vaani/features/explore/view/search_result_page.dart'; import 'package:vaani/features/item_viewer/view/library_item_page.dart'; -import 'package:vaani/features/library_browser/view/library_browser_page.dart'; import 'package:vaani/features/logging/view/logs_page.dart'; import 'package:vaani/features/onboarding/view/callback_page.dart'; import 'package:vaani/features/onboarding/view/onboarding_single_page.dart'; diff --git a/lib/router/scaffold_with_nav_bar.dart b/lib/router/scaffold_with_nav_bar.dart index a34d991..945e935 100644 --- a/lib/router/scaffold_with_nav_bar.dart +++ b/lib/router/scaffold_with_nav_bar.dart @@ -41,7 +41,7 @@ class ScaffoldWithNavBar extends HookConsumerWidget { alignment: Alignment.bottomCenter, children: [ isVertical ? navigationShell : buildNavLeft(context, ref), - Hero(tag: 'player_hero', child: const PlayerMinimized()), + const PlayerMinimized(), ], ), bottomNavigationBar: isVertical ? buildNavBottom(context, ref) : null, diff --git a/lib/shared/audio_player.dart b/lib/shared/audio_player.dart deleted file mode 100644 index d8dce4e..0000000 --- a/lib/shared/audio_player.dart +++ /dev/null @@ -1,336 +0,0 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:async'; - -import 'package:audio_service/audio_service.dart'; -import 'package:collection/collection.dart'; -import 'package:just_audio/just_audio.dart'; -import 'package:logging/logging.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:shelfsdk/audiobookshelf_api.dart'; - -import 'package:vaani/features/settings/app_settings_provider.dart'; -import 'package:vaani/features/settings/models/app_settings.dart'; -import 'package:vaani/shared/extensions/chapter.dart'; -import 'package:vaani/shared/extensions/model_conversions.dart'; - -final offset = Duration(milliseconds: 10); - -final _logger = Logger('AbsAudioPlayer'); - -abstract class AbsAudioPlayer { - final _mediaItemController = BehaviorSubject.seeded(null); - final playerStateSubject = - BehaviorSubject.seeded(AbsPlayerState(false, AbsProcessingState.idle)); - final _bookStreamController = BehaviorSubject.seeded(null); - final _chapterStreamController = BehaviorSubject.seeded(null); - - BookExpanded? get book => _bookStreamController.nvalue; - BookChapter? get currentChapter => _chapterStreamController.nvalue; - AbsPlayerState get playerState => playerStateSubject.value; - Stream get mediaItemStream => _mediaItemController.stream; - Stream get playerStateStream => playerStateSubject.stream; - - Future load( - BookExpanded book, { - required Uri baseUrl, - required String token, - Duration? initialPosition, - List? downloadedUris, - }) async { - if (_bookStreamController.nvalue == book) { - _logger.info('Book is the same, doing nothing'); - return; - } - _bookStreamController.add(book); - final appSettings = loadOrCreateAppSettings(); - - final currentTrack = book.findTrackAtTime(initialPosition ?? Duration.zero); - final indexTrack = book.tracks.indexOf(currentTrack); - final positionInTrack = initialPosition != null - ? initialPosition - currentTrack.startOffset - : null; - final title = appSettings.notificationSettings.primaryTitle - .formatNotificationTitle(book); - final artist = appSettings.notificationSettings.secondaryTitle - .formatNotificationTitle(book); - _chapterStreamController - .add(book.findChapterAtTime(initialPosition ?? Duration.zero)); - final item = MediaItem( - id: book.libraryItemId, - title: title, - artist: artist, - duration: currentChapter?.duration ?? book.duration, - artUri: Uri.parse( - '$baseUrl/api/items/${book.libraryItemId}/cover?token=$token', - ), - ); - _mediaItemController.sink.add(item); - final playlist = book.tracks - .map( - (track) => _getUri(currentTrack, downloadedUris, - baseUrl: baseUrl, token: token), - ) - .toList(); - await setPlayList(playlist, index: indexTrack, position: positionInTrack); - } - - Future setPlayList( - List playlist, { - int? index, - Duration? position, - }); - Future play(); - Future pause(); - Future playOrPause(); - - // 跳到下一章 - Future next() async { - final chapter = currentChapter; - if (book == null || chapter == null) { - return; - } - final chapterIndex = book!.chapters.indexOf(chapter); - if (chapterIndex < book!.chapters.length - 1) { - final nextChapter = book!.chapters[chapterIndex + 1]; - await switchChapter(nextChapter.id); - } - } - - // 跳到上一章 - Future previous() async { - final chapter = currentChapter; - if (book == null || chapter == null) { - return; - } - final currentIndex = book!.chapters.indexOf(chapter); - if (currentIndex > 0) { - final prevChapter = book!.chapters[currentIndex - 1]; - await switchChapter(prevChapter.id); - } else { - // 已经是第一章,回到开头 - await seekInBook(Duration.zero); - } - } - - Future seek(Duration position, {int? index}); - Future seekInBook(Duration position) async { - if (book == null) return; - // 找到目标位置所在音轨和音轨内的位置 - final track = book!.findTrackAtTime(position); - final index = book!.tracks.indexOf(track); - Duration positionInTrack = position - track.startOffset; - if (positionInTrack <= Duration.zero) { - positionInTrack = offset; - } - // 切换到目标音轨具体位置 - await seek(positionInTrack, index: index); - } - - Future setSpeed(double speed); - Future setVolume(double volume); - Future switchChapter(int chapterId) async { - if (book == null) return; - - final chapter = book!.chapters.firstWhere( - (ch) => ch.id == chapterId, - orElse: () => throw Exception('Chapter not found'), - ); - await seekInBook(chapter.start + offset); - } - - bool get playing => playerState.playing; - Stream get playingStream; - Stream get bookStream => _bookStreamController.stream; - Stream get chapterStream => _chapterStreamController.stream; - - int get currentIndex; - double get speed; - - Duration get position; - Stream get positionStream; - - Duration get positionInChapter { - final globalPosition = positionInBook; - return globalPosition - - (book?.findChapterAtTime(globalPosition).start ?? Duration.zero); - } - - Duration get positionInBook => - position + (book?.tracks[currentIndex].startOffset ?? Duration.zero); - - Stream get positionInChapterStream => - positionStream.map((position) { - return positionInChapter; - }); - - Stream get positionInBookStream => positionStream.map((position) { - return positionInBook; - }); - - Duration get bufferedPosition; - Stream get bufferedPositionStream; - Duration get bufferedPositionInBook => - bufferedPosition + - (book?.tracks[currentIndex].startOffset ?? Duration.zero); - Stream get bufferedPositionInBookStream => - bufferedPositionStream.map((position) { - return bufferedPositionInBook; - }); - - dispose() { - _mediaItemController.close(); - playerStateSubject.close(); - _bookStreamController.close(); - _chapterStreamController.close(); - } -} - -/// Enumerates the different processing states of a player. -enum AbsProcessingState { - /// The player has not loaded an [AudioSource]. - idle, - - /// The player is loading an [AudioSource]. - loading, - - /// The player is buffering audio and unable to play. - buffering, - - /// The player is has enough audio buffered and is able to play. - ready, - - /// The player has reached the end of the audio. - completed, -} - -/// Encapsulates the playing and processing states. These two states vary -/// orthogonally, and so if [processingState] is [ProcessingState.buffering], -/// you can check [playing] to determine whether the buffering occurred while -/// the player was playing or while the player was paused. -class AbsPlayerState { - /// Whether the player will play when [processingState] is - /// [ProcessingState.ready]. - final bool playing; - - /// The current processing state of the player. - final AbsProcessingState processingState; - - AbsPlayerState(this.playing, this.processingState); - - @override - String toString() => 'playing=$playing,processingState=$processingState'; - - @override - int get hashCode => Object.hash(playing, processingState); - - @override - bool operator ==(Object other) => - other.runtimeType == runtimeType && - other is PlayerState && - other.playing == playing && - other.processingState == processingState; - - AbsPlayerState copyWith({ - bool? playing, - AbsProcessingState? processingState, - }) { - return AbsPlayerState( - playing ?? this.playing, - processingState ?? this.processingState, - ); - } -} - -Uri _getUri( - AudioTrack track, - List? downloadedUris, { - required Uri baseUrl, - required String token, -}) { - // check if the track is in the downloadedUris - final uri = downloadedUris?.firstWhereOrNull( - (element) { - return element.pathSegments.last == track.metadata?.filename; - }, - ); - - return uri ?? - Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token'); -} - -/// Backwards compatible extensions on rxdart's ValueStream -extension _ValueStreamExtension on ValueStream { - /// Backwards compatible version of valueOrNull. - T? get nvalue => hasValue ? value : null; -} - -extension FormatNotificationTitle on String { - String formatNotificationTitle(BookExpanded book) { - return replaceAllMapped( - RegExp(r'\$(\w+)'), - (match) { - final type = match.group(1); - return NotificationTitleType.values - .firstWhere((element) => element.name == type) - .extractFrom(book) ?? - match.group(0) ?? - ''; - }, - ); - } -} - -extension NotificationTitleUtils on NotificationTitleType { - String? extractFrom(BookExpanded book) { - var bookMetadataExpanded = book.metadata.asBookMetadataExpanded; - switch (this) { - case NotificationTitleType.bookTitle: - return bookMetadataExpanded.title; - case NotificationTitleType.chapterTitle: - // TODO: implement chapter title; depends on https://github.com/Dr-Blank/Vaani/issues/2 - return bookMetadataExpanded.title; - case NotificationTitleType.author: - return bookMetadataExpanded.authorName; - case NotificationTitleType.narrator: - return bookMetadataExpanded.narratorName; - case NotificationTitleType.series: - return bookMetadataExpanded.seriesName; - case NotificationTitleType.subtitle: - return bookMetadataExpanded.subtitle; - case NotificationTitleType.year: - return bookMetadataExpanded.publishedYear; - } - } -} - -extension BookExpandedExtension on BookExpanded { - BookChapter findChapterAtTime(Duration position) { - return chapters.firstWhere( - (element) { - return element.start <= position && element.end >= position + offset; - }, - orElse: () => chapters.first, - ); - } - - AudioTrack findTrackAtTime(Duration position) { - return tracks.firstWhere( - (element) { - return element.startOffset <= position && - element.startOffset + element.duration >= position + offset; - }, - orElse: () => tracks.first, - ); - } - - int findTrackIndexAtTime(Duration position) { - return tracks.indexWhere((element) { - return element.startOffset <= position && - element.startOffset + element.duration >= position + offset; - }); - } - - Duration getTrackStartOffset(int index) { - return tracks[index].startOffset; - } -} diff --git a/lib/shared/widgets/drawer.dart b/lib/shared/widgets/drawer.dart index 832b379..12aa1c2 100644 --- a/lib/shared/widgets/drawer.dart +++ b/lib/shared/widgets/drawer.dart @@ -25,12 +25,11 @@ class MyDrawer extends StatelessWidget { ListTile( title: const Text('server Settings'), onTap: () { - // Navigator.of(context).push( - // PageRoute( - // context: context, - // builder: (context) => const ServerManagerPage(), - // ), - // ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const ServerManagerPage(), + ), + ); }, ), ListTile( diff --git a/lib/shared/widgets/shelves/book_shelf.dart b/lib/shared/widgets/shelves/book_shelf.dart index 1c1160c..430fa50 100644 --- a/lib/shared/widgets/shelves/book_shelf.dart +++ b/lib/shared/widgets/shelves/book_shelf.dart @@ -294,7 +294,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { // book.media.asBookExpanded, // userProgress?.currentTime, // ); - ref.read(absAudioPlayerProvider.notifier).load( + ref.read(audioPlayerProvider.notifier).load( book.media.asBookExpanded, initialPosition: userProgress?.currentTime, ); diff --git a/lib/shared/widgets/tray_manager.dart b/lib/shared/widgets/tray_manager.dart index aa9bcf0..0d6043b 100644 --- a/lib/shared/widgets/tray_manager.dart +++ b/lib/shared/widgets/tray_manager.dart @@ -46,17 +46,17 @@ class _TrayManagerState extends ConsumerState MenuItem( key: 'play_pause', label: '播放/暂停', - onClick: (menuItem) => ref.read(absAudioPlayerProvider).playOrPause(), + onClick: (menuItem) => ref.read(audioPlayerProvider).playOrPause(), ), MenuItem( key: 'previous', label: '上一个', - onClick: (menuItem) => ref.read(absAudioPlayerProvider).previous(), + onClick: (menuItem) => ref.read(audioPlayerProvider).previous(), ), MenuItem( key: 'next', label: '下一个', - onClick: (menuItem) => ref.read(absAudioPlayerProvider).next(), + onClick: (menuItem) => ref.read(audioPlayerProvider).next(), ), MenuItem.separator(), MenuItem( diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index c46c2fb..4038244 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,10 +9,8 @@ import audio_service import audio_session import device_info_plus import dynamic_color -import file_picker import isar_flutter_libs import just_audio -import media_kit_libs_macos_audio import package_info_plus import path_provider_foundation import screen_retriever_macos @@ -27,10 +25,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) - FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin")) - MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) ScreenRetrieverMacosPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverMacosPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 7ff954c..7874d54 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -95,7 +95,7 @@ packages: source: hosted version: "0.1.4" audio_session: - dependency: transitive + dependency: "direct main" description: name: audio_session sha256: "2b7fff16a552486d078bfc09a8cde19f426dc6d6329262b684182597bec5b1ac" @@ -110,14 +110,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - auto_scroll_text: - dependency: "direct main" - description: - name: auto_scroll_text - sha256: "8de28056f844f24f13771606417ffa109397f75a66440fe60ec2d38c133e16dc" - url: "https://pub.dev" - source: hosted - version: "0.0.7" background_downloader: dependency: "direct main" description: @@ -262,14 +254,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" - coast: - dependency: "direct main" - description: - name: coast - sha256: a85fdf09a387ea511ce790a79a11e673b8ee05e1ac142e17e0fde666a8fda853 - url: "https://pub.dev" - source: hosted - version: "2.0.2" code_builder: dependency: transitive description: @@ -318,14 +302,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" custom_lint: dependency: "direct dev" description: @@ -366,14 +342,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.8" - dbus: - dependency: transitive - description: - name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.dev" - source: hosted - version: "0.7.11" device_info_plus: dependency: "direct main" description: @@ -422,14 +390,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.1" - easy_stepper: - dependency: "direct main" - description: - name: easy_stepper - sha256: "63f66314a509ec690c8152a41288961fd96ba9e92ef184299f068a5e78bd16ad" - url: "https://pub.dev" - source: hosted - version: "0.8.5+1" fake_async: dependency: transitive description: @@ -454,14 +414,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" - file_picker: - dependency: "direct main" - description: - name: file_picker - sha256: "7872545770c277236fd32b022767576c562ba28366204ff1a5628853cf8f2200" - url: "https://pub.dev" - source: hosted - version: "10.3.7" fixnum: dependency: transitive description: @@ -528,14 +480,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_plugin_android_lifecycle: - dependency: transitive - description: - name: flutter_plugin_android_lifecycle - sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 - url: "https://pub.dev" - source: hosted - version: "2.0.31" flutter_riverpod: dependency: transitive description: @@ -706,14 +650,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.4" - infinite_listview: - dependency: transitive - description: - name: infinite_listview - sha256: f6062c1720eb59be553dfa6b89813d3e8dd2f054538445aaa5edaddfa5195ce6 - url: "https://pub.dev" - source: hosted - version: "1.1.0" intl: dependency: transitive description: @@ -778,6 +714,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.5" + just_audio_media_kit: + dependency: "direct main" + description: + name: just_audio_media_kit + sha256: f3cf04c3a50339709e87e90b4e841eef4364ab4be2bdbac0c54cc48679f84d23 + url: "https://pub.dev" + source: hosted + version: "2.1.0" just_audio_platform_interface: dependency: transitive description: @@ -891,55 +835,23 @@ packages: source: hosted version: "0.11.1" media_kit: - dependency: "direct main" + dependency: transitive description: name: media_kit sha256: "2a207ea7baf1a2ea2ff2016d512e572ca6fc02a937769effb5c27b4d682b4a53" url: "https://pub.dev" source: hosted version: "1.2.3" - media_kit_libs_android_audio: - dependency: transitive - description: - name: media_kit_libs_android_audio - sha256: "8f8f9759e537e12d66f08bc4d5279eb1bb21a0ccc519ff3442c68a9f3b6dd68b" - url: "https://pub.dev" - source: hosted - version: "1.3.8" - media_kit_libs_audio: - dependency: "direct main" - description: - name: media_kit_libs_audio - sha256: "81bf506c234e81e3ec536ba72f8f700a928543c14c345220210cae0411636316" - url: "https://pub.dev" - source: hosted - version: "1.0.7" - media_kit_libs_ios_audio: - dependency: transitive - description: - name: media_kit_libs_ios_audio - sha256: "78ccf04e27d6b4ba00a355578ccb39b772f00d48269a6ac3db076edf2d51934f" - url: "https://pub.dev" - source: hosted - version: "1.1.4" media_kit_libs_linux: - dependency: transitive + dependency: "direct main" description: name: media_kit_libs_linux sha256: "2b473399a49ec94452c4d4ae51cfc0f6585074398d74216092bf3d54aac37ecf" url: "https://pub.dev" source: hosted version: "1.2.1" - media_kit_libs_macos_audio: - dependency: transitive - description: - name: media_kit_libs_macos_audio - sha256: "3be21844df98f286de32808592835073cdef2c1a10078bac135da790badca950" - url: "https://pub.dev" - source: hosted - version: "1.1.4" media_kit_libs_windows_audio: - dependency: transitive + dependency: "direct main" description: name: media_kit_libs_windows_audio sha256: c2fd558cc87b9d89a801141fcdffe02e338a3b21a41a18fbd63d5b221a1b8e53 @@ -970,14 +882,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - numberpicker: - dependency: "direct main" - description: - name: numberpicker - sha256: "4c129154944b0f6b133e693f8749c3f8bfb67c4d07ef9dcab48b595c22d1f156" - url: "https://pub.dev" - source: hosted - version: "2.1.2" octo_image: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 78682f4..49a1068 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,25 +31,26 @@ isar_version: &isar_version ^4.0.0-dev.14 # define the version to be used # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: + collection: ^1.18.0 + flutter: + sdk: flutter + animated_list_plus: ^0.5.2 animated_theme_switcher: ^2.0.10 archive: ^4.0.5 audio_video_progress_bar: ^2.0.2 - auto_scroll_text: ^0.0.7 + # auto_scroll_text: ^0.0.7 background_downloader: ^9.2.0 cached_network_image: ^3.3.1 - coast: ^2.0.2 - collection: ^1.18.0 - cupertino_icons: ^1.0.6 + # coast: ^2.0.2 + # cupertino_icons: ^1.0.6 # flutter_platform_widgets: ^9.0.0 flutter_staggered_grid_view: ^0.7.0 - device_info_plus: ^11.3.3 duration_picker: ^1.2.0 dynamic_color: ^1.7.0 - easy_stepper: ^0.8.4 - file_picker: ^10.0.0 - flutter: - sdk: flutter + # easy_stepper: ^0.8.4 + # file_picker: ^10.0.0 + flutter_animate: ^4.5.0 flutter_cache_manager: ^3.3.2 flutter_hooks: ^0.21.2 @@ -62,6 +63,8 @@ dependencies: isar: ^4.0.0-dev.14 isar_flutter_libs: ^4.0.0-dev.14 json_annotation: ^4.9.0 + audio_service: ^0.18.15 + audio_session: ^0.1.23 just_audio: ^0.10.5 # just_audio_background: # # TODO Remove git dep when https://github.com/ryanheise/just_audio/issues/912 is closed @@ -70,13 +73,11 @@ dependencies: # ref: media-notification-config # path: just_audio_background # just_audio_windows: ^0.2.2 - # just_audio_media_kit: ^2.0.4 - # media_kit_libs_linux: any - # media_kit_libs_windows_audio: any - audio_service: ^0.18.15 - # audio_session: ^0.1.23 - media_kit: ^1.2.3 # Primary package. - media_kit_libs_audio: any # Native audio dependencies. + just_audio_media_kit: ^2.0.4 + media_kit_libs_linux: any + media_kit_libs_windows_audio: any + # media_kit: ^1.2.3 # Primary package. + # media_kit_libs_audio: any # Native audio dependencies. list_wheel_scroll_view_nls: ^0.0.3 logging: ^1.2.0 @@ -88,7 +89,8 @@ dependencies: # git: # url: https://github.com/Dr-Blank/miniplayer.git # ref: feat-notifier-for-percent-dismissed - numberpicker: ^2.1.2 + # numberpicker: ^2.1.2 + device_info_plus: ^11.3.3 package_info_plus: ^8.0.0 path: ^1.9.0 path_provider: ^2.1.0