From 50a27fdf67395642b948fbd24b58a65b4fce206b Mon Sep 17 00:00:00 2001 From: rang <378694192@qq.com> Date: Mon, 8 Dec 2025 23:46:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=92=AD=E6=94=BE=E9=80=BB=E8=BE=91=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/library_item_actions.dart | 10 +- .../view/library_item_hero_section.dart | 6 +- .../core/playback_reporter.dart | 6 +- .../core/playback_reporter_session.dart | 4 +- .../providers/playback_reporter_provider.dart | 4 +- .../playback_reporter_provider.g.dart | 2 +- .../player/core/abs_audio_handler.dart | 14 +- .../player/core/abs_audio_player.dart | 60 +- .../player/core/audiobook_player.dart | 838 +++++++++--------- .../player/providers/abs_provider.dart | 16 +- .../player/providers/abs_provider.g.dart | 6 +- .../player/providers/audiobook_player.dart | 198 ++--- .../player/providers/audiobook_player.g.dart | 41 - .../providers/currently_playing_provider.dart | 20 +- .../providers/player_status_provider.dart | 66 +- .../providers/player_status_provider.g.dart | 25 - .../view/mini_player_bottom_padding.dart | 4 +- .../player/view/player_expanded_desktop.dart | 3 +- .../widgets/chapter_selection_button.dart | 5 +- .../widgets/player_player_pause_button.dart | 8 +- .../view/widgets/player_progress_bar.dart | 1 - .../player/view/widgets/speed_selector.dart | 4 +- .../shake_detector_provider.dart | 26 +- .../shake_detector_provider.g.dart | 2 +- .../skip_start_end/core/skip_start_end.dart | 8 +- .../providers/skip_start_end_provider.dart | 3 +- .../providers/skip_start_end_provider.g.dart | 2 +- .../sleep_timer/core/sleep_timer.dart | 10 +- .../providers/sleep_timer_provider.dart | 6 +- .../providers/sleep_timer_provider.g.dart | 2 +- lib/shared/audio_player.dart | 23 +- lib/shared/audio_player_mpv.dart | 30 +- lib/shared/audio_player_platform.dart | 96 ++ 33 files changed, 788 insertions(+), 761 deletions(-) delete mode 100644 lib/features/player/providers/audiobook_player.g.dart delete mode 100644 lib/features/player/providers/player_status_provider.g.dart create mode 100644 lib/shared/audio_player_platform.dart diff --git a/lib/features/item_viewer/view/library_item_actions.dart b/lib/features/item_viewer/view/library_item_actions.dart index 1b652e3..d56459f 100644 --- a/lib/features/item_viewer/view/library_item_actions.dart +++ b/lib/features/item_viewer/view/library_item_actions.dart @@ -15,8 +15,6 @@ import 'package:vaani/features/downloads/providers/download_manager.dart' itemDownloadProgressProvider; import 'package:vaani/features/item_viewer/view/library_item_page.dart'; import 'package:vaani/features/player/providers/abs_provider.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; -import 'package:vaani/features/player/providers/player_status_provider.dart'; import 'package:vaani/features/settings/api_settings_provider.dart'; import 'package:vaani/generated/l10n.dart'; import 'package:vaani/globals.dart'; @@ -435,12 +433,12 @@ class _LibraryItemPlayButton extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final currentBook = ref.watch(currentBookProvider); final book = item.media.asBookExpanded; - final playerStatusNotifier = ref.watch(playerStatusProvider); - final isLoading = playerStatusNotifier.isLoading(book.libraryItemId); + final playerStateNotifier = ref.watch(playerStateProvider.notifier); + final isLoading = playerStateNotifier.isLoading(book.libraryItemId); final isCurrentBookSetInPlayer = currentBook?.libraryItemId == book.libraryItemId; final isPlayingThisBook = - playerStatusNotifier.isPlaying() && isCurrentBookSetInPlayer; + playerStateNotifier.isPlaying() && isCurrentBookSetInPlayer; final userMediaProgress = item.userMediaProgress; final isBookCompleted = userMediaProgress?.isFinished ?? false; @@ -469,7 +467,7 @@ class _LibraryItemPlayButton extends HookConsumerWidget { return ElevatedButton.icon( onPressed: () { currentBook?.libraryItemId == book.libraryItemId - ? ref.read(playerProvider).togglePlayPause() + ? ref.read(absAudioPlayerProvider).playOrPause() : ref.read(absAudioPlayerProvider.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 24da9c0..a1cc384 100644 --- a/lib/features/item_viewer/view/library_item_hero_section.dart +++ b/lib/features/item_viewer/view/library_item_hero_section.dart @@ -7,9 +7,9 @@ import 'package:vaani/api/image_provider.dart'; import 'package:vaani/api/library_item_provider.dart'; import 'package:vaani/constants/hero_tag_conventions.dart'; import 'package:vaani/features/item_viewer/view/library_item_page.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; -import 'package:vaani/router/models/library_item_extras.dart'; +import 'package:vaani/features/player/providers/abs_provider.dart'; import 'package:vaani/features/settings/app_settings_provider.dart'; +import 'package:vaani/router/models/library_item_extras.dart'; import 'package:vaani/shared/extensions/duration_format.dart'; import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/widgets/shelves/book_shelf.dart'; @@ -139,7 +139,7 @@ class _LibraryItemProgressIndicator extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final player = ref.watch(playerProvider); + final player = ref.watch(absAudioPlayerProvider); 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 9c46cb9..c83426f 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/features/player/core/audiobook_player.dart'; +import 'package:vaani/shared/audio_player.dart'; import 'package:vaani/shared/extensions/obfuscation.dart'; final _logger = Logger('PlaybackReporter'); @@ -14,7 +14,7 @@ final _logger = Logger('PlaybackReporter'); /// and also report when the player is paused/stopped/finished/playing class PlaybackReporter { /// The player to watch - final AbsAudioHandler player; + final AbsAudioPlayer player; /// the api to report to final AudiobookshelfApi authenticatedApi; @@ -75,7 +75,7 @@ class PlaybackReporter { this.markCompleteWhenTimeLeft = const Duration(seconds: 5), }) : _reportingInterval = reportingInterval { // initial conditions - if (player.player.playing) { + if (player.playing) { _stopwatch.start(); _setReportTimerIfNotAlready(); _logger.fine('starting stopwatch'); diff --git a/lib/features/playback_reporting/core/playback_reporter_session.dart b/lib/features/playback_reporting/core/playback_reporter_session.dart index d1a3c76..3b0842f 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/features/player/core/audiobook_player.dart'; +import 'package:vaani/shared/audio_player.dart'; import 'package:vaani/shared/extensions/obfuscation.dart'; final _logger = Logger('PlaybackReporter'); @@ -14,7 +14,7 @@ final _logger = Logger('PlaybackReporter'); /// and also report when the player is paused/stopped/finished/playing class PlaybackReporter { /// The player to watch - final AbsAudioHandler player; + final AbsAudioPlayer player; /// the api to report to final AudiobookshelfApi authenticatedApi; diff --git a/lib/features/playback_reporting/providers/playback_reporter_provider.dart b/lib/features/playback_reporting/providers/playback_reporter_provider.dart index 8688b2b..a85cf1a 100644 --- a/lib/features/playback_reporting/providers/playback_reporter_provider.dart +++ b/lib/features/playback_reporting/providers/playback_reporter_provider.dart @@ -2,7 +2,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:vaani/api/api_provider.dart'; import 'package:vaani/features/playback_reporting/core/playback_reporter.dart' as core; -import 'package:vaani/features/player/providers/audiobook_player.dart'; +import 'package:vaani/features/player/providers/abs_provider.dart'; import 'package:vaani/features/settings/app_settings_provider.dart'; import 'package:vaani/globals.dart'; @@ -13,7 +13,7 @@ class PlaybackReporter extends _$PlaybackReporter { @override Future build() async { final playerSettings = ref.watch(appSettingsProvider).playerSettings; - final player = ref.watch(playerProvider); + final player = ref.watch(absAudioPlayerProvider); 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 320a962..981782c 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'23b9770d279d921a5766fc2dda20f76dd3e181ed'; +String _$playbackReporterHash() => r'13936a7d616e9055388d23fa361519b749c524a3'; /// 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 b7d1fdd..acc9e81 100644 --- a/lib/features/player/core/abs_audio_handler.dart +++ b/lib/features/player/core/abs_audio_handler.dart @@ -1,8 +1,4 @@ import 'package:audio_service/audio_service.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:media_kit/media_kit.dart'; -import 'package:vaani/features/player/providers/abs_provider.dart' - hide AbsAudioPlayer; import 'package:vaani/shared/audio_player.dart'; class AbsAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { @@ -39,11 +35,11 @@ class AbsAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { playing: playerState.playing, // 根据 playing 和实际情况更新 processingState processingState: const { - ProcessingState.idle: AudioProcessingState.idle, - ProcessingState.loading: AudioProcessingState.loading, - ProcessingState.buffering: AudioProcessingState.buffering, - ProcessingState.ready: AudioProcessingState.ready, - ProcessingState.completed: AudioProcessingState.completed, + AbsProcessingState.idle: AudioProcessingState.idle, + AbsProcessingState.loading: AudioProcessingState.loading, + AbsProcessingState.buffering: AudioProcessingState.buffering, + AbsProcessingState.ready: AudioProcessingState.ready, + AbsProcessingState.completed: AudioProcessingState.completed, }[playerState.processingState] ?? AudioProcessingState.idle, ), diff --git a/lib/features/player/core/abs_audio_player.dart b/lib/features/player/core/abs_audio_player.dart index 14ee0fe..4450782 100644 --- a/lib/features/player/core/abs_audio_player.dart +++ b/lib/features/player/core/abs_audio_player.dart @@ -1,33 +1,33 @@ -// 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 'package:just_audio/just_audio.dart'; +// import 'package:shelfsdk/audiobookshelf_api.dart' as api; -class AbsPlayerState { - api.BookExpanded? book; - // 当前章节 - final api.BookChapter? currentChapter; - // 当前音轨序号 - final int currentIndex; - final bool playing; +// class AbsPlayerState { +// api.BookExpanded? book; +// // 当前章节 +// final api.BookChapter? currentChapter; +// // 当前音轨序号 +// final int currentIndex; +// final bool playing; - AbsPlayerState({ - this.book, - this.currentChapter, - this.currentIndex = 0, - this.playing = false, - }); +// AbsPlayerState({ +// this.book, +// this.currentChapter, +// this.currentIndex = 0, +// this.playing = false, +// }); - 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, - ); - } -} +// 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, +// ); +// } +// } diff --git a/lib/features/player/core/audiobook_player.dart b/lib/features/player/core/audiobook_player.dart index f464b9f..06d62b8 100644 --- a/lib/features/player/core/audiobook_player.dart +++ b/lib/features/player/core/audiobook_player.dart @@ -1,468 +1,468 @@ -// my_audio_handler.dart -import 'dart:io'; +// // 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'; +// 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); +// // 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'); +// final _logger = Logger('AudiobookPlayer'); -class AbsAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { - final AudioPlayer _player = AudioPlayer(); - final Ref ref; +// class AbsAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { +// final AudioPlayer _player = AudioPlayer(); +// final Ref ref; - BookExpanded? _book; - BookExpanded? get book => _book; +// BookExpanded? _book; +// BookExpanded? get book => _book; - late NotificationSettings notificationSettings; +// 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); +// 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); - } - }); - } +// // 转发播放状态 +// _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; +// // 加载有声书 +// 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); +// 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(); +// // 添加所有音轨 +// 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(); +// 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); - // } - } +// // 恢复上次播放位置(如果有) +// // if (initialPosition != null) { +// // await seekInBook(initialPosition); +// // } +// } - // // 音轨切换处理 - // void _onTrackChanged(int trackIndex) { - // if (_book == null) return; +// // // 音轨切换处理 +// // void _onTrackChanged(int trackIndex) { +// // if (_book == null) return; - // // 可以在这里处理音轨切换逻辑,比如预加载下一音轨 - // // print('切换到音轨: ${_book!.tracks[trackIndex].title}'); - // } +// // // 可以在这里处理音轨切换逻辑,比如预加载下一音轨 +// // // print('切换到音轨: ${_book!.tracks[trackIndex].title}'); +// // } - // 核心功能:跳转到指定章节 - Future skipToChapter(int chapterId) async { - if (_book == null) return; +// // 核心功能:跳转到指定章节 +// 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); - } +// final chapter = _book!.chapters.firstWhere( +// (ch) => ch.id == chapterId, +// orElse: () => throw Exception('Chapter not found'), +// ); +// await seekInBook(chapter.start + offset); +// } - BookExpanded? get Book => _book; +// BookExpanded? get Book => _book; - // 当前音轨 - AudioTrack? get currentTrack { - if (_book == null || _player.currentIndex == null) { - return null; - } - return _book!.tracks[_player.currentIndex!]; - } +// // 当前音轨 +// AudioTrack? get currentTrack { +// if (_book == null || _player.currentIndex == null) { +// return null; +// } +// return _book!.tracks[_player.currentIndex!]; +// } - // 当前章节 - BookChapter? get currentChapter { - return _currentChapterObject.value; - } +// // 当前章节 +// 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 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 positionInBook { +// return _player.position + (currentTrack?.startOffset ?? Duration.zero); +// } - Duration get bufferedPositionInBook { - return _player.bufferedPosition + - (currentTrack?.startOffset ?? Duration.zero); - } +// Duration get bufferedPositionInBook { +// return _player.bufferedPosition + +// (currentTrack?.startOffset ?? Duration.zero); +// } - Duration? get chapterDuration => currentChapter?.duration; +// Duration? get chapterDuration => currentChapter?.duration; - Stream get playerStateStream => _player.playerStateStream; +// Stream get playerStateStream => _player.playerStateStream; - Stream get positionStream => _player.positionStream; +// Stream get positionStream => _player.positionStream; - Stream get positionStreamInBook { - return _player.positionStream.map((position) { - return position + (currentTrack?.startOffset ?? Duration.zero); - }); - } +// 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 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 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 positionStreamInChapter { +// return _player.positionStream.distinct().map((position) { +// return position + +// (currentTrack?.startOffset ?? Duration.zero) - +// (currentChapter?.start ?? Duration.zero); +// }); +// } - Stream get chapterStream => _currentChapterObject.stream; +// 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(), - }; - } +// 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 play() async { +// await _player.play(); +// } - @override - Future pause() async { - await _player.pause(); - } +// @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 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 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); - } +// @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); - } +// Future setVolume(double volume) async { +// await _player.setVolume(volume); +// } - @override - Future setSpeed(double speed) async { - await _player.setSpeed(speed); - } +// @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); - } +// // 核心功能:跳转到全局时间位置 +// 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, - ); - } -} +// 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; - }, - ); +// 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'); -} +// 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 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 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, - ); - } +// 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, - ); - } +// 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; - }); - } +// 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; - } -} +// Duration getTrackStartOffset(int index) { +// return tracks[index].startOffset; +// } +// } diff --git a/lib/features/player/providers/abs_provider.dart b/lib/features/player/providers/abs_provider.dart index 438c1dc..b0f78d4 100644 --- a/lib/features/player/providers/abs_provider.dart +++ b/lib/features/player/providers/abs_provider.dart @@ -1,5 +1,4 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:logging/logging.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:shelfsdk/audiobookshelf_api.dart' as api; import 'package:vaani/api/api_provider.dart'; @@ -8,7 +7,7 @@ import 'package:vaani/shared/audio_player_mpv.dart'; part 'abs_provider.g.dart'; -final _logger = Logger('AbsPlayerProvider'); +// final _logger = Logger('AbsPlayerProvider'); @Riverpod(keepAlive: true) class AbsAudioPlayer extends _$AbsAudioPlayer { @@ -40,7 +39,7 @@ class AbsAudioPlayer extends _$AbsAudioPlayer { @riverpod class PlayerState extends _$PlayerState { @override - core.PlayerState build() { + core.AbsPlayerState build() { final player = ref.read(absAudioPlayerProvider); player.playerStateStream.listen((playerState) { if (playerState != state) { @@ -49,6 +48,17 @@ class PlayerState extends _$PlayerState { }); return player.playerState; } + + bool isLoading(String itemId) { + final player = ref.read(absAudioPlayerProvider); + return player.book?.libraryItemId == itemId && + !state.playing && + state.processingState == core.AbsProcessingState.loading; + } + + bool isPlaying() { + return state.playing; + } } @riverpod diff --git a/lib/features/player/providers/abs_provider.g.dart b/lib/features/player/providers/abs_provider.g.dart index d76ccd1..5ade537 100644 --- a/lib/features/player/providers/abs_provider.g.dart +++ b/lib/features/player/providers/abs_provider.g.dart @@ -74,12 +74,12 @@ final absAudioPlayerProvider = ); typedef _$AbsAudioPlayer = Notifier; -String _$playerStateHash() => r'ed07c487fdad5fd0e21dfd32a274eecc470e83a4'; +String _$playerStateHash() => r'6635671077b077f48dad173c4393462921de56f8'; /// See also [PlayerState]. @ProviderFor(PlayerState) final playerStateProvider = - AutoDisposeNotifierProvider.internal( + AutoDisposeNotifierProvider.internal( PlayerState.new, name: r'playerStateProvider', debugGetCreateSourceHash: @@ -88,7 +88,7 @@ final playerStateProvider = allTransitiveDependencies: null, ); -typedef _$PlayerState = AutoDisposeNotifier; +typedef _$PlayerState = AutoDisposeNotifier; String _$currentBookHash() => r'40c24ad45aab37afc32e8e8383d6abbe19b714bc'; /// See also [CurrentBook]. diff --git a/lib/features/player/providers/audiobook_player.dart b/lib/features/player/providers/audiobook_player.dart index c8e459c..77be45f 100644 --- a/lib/features/player/providers/audiobook_player.dart +++ b/lib/features/player/providers/audiobook_player.dart @@ -1,108 +1,108 @@ -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'; +// 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; - } -} +// part 'audiobook_player.g.dart'; // @Riverpod(keepAlive: true) -// class Session extends _$Session { +// 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 -// 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, -// ), -// ]); +// AbsAudioHandler build() { +// return ref.watch(audioHandlerInitProvider).requireValue; // } // } -class PlaybackSyncError implements Exception { - String message; +// // @Riverpod(keepAlive: true) +// // class Session extends _$Session { +// // @override +// // core.PlaybackSessionExpanded? build() { +// // return null; +// // } - PlaybackSyncError([this.message = 'Error syncing playback']); +// // 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); - @override - String toString() { - return 'PlaybackSyncError: $message'; - } -} +// // 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/audiobook_player.g.dart b/lib/features/player/providers/audiobook_player.g.dart deleted file mode 100644 index 6187d84..0000000 --- a/lib/features/player/providers/audiobook_player.g.dart +++ /dev/null @@ -1,41 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'audiobook_player.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$audioHandlerInitHash() => r'80f9912c0a93b4bbfe7da3967966065e78f2f6c2'; - -/// See also [audioHandlerInit]. -@ProviderFor(audioHandlerInit) -final audioHandlerInitProvider = FutureProvider.internal( - audioHandlerInit, - name: r'audioHandlerInitProvider', - debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') - ? null - : _$audioHandlerInitHash, - dependencies: null, - allTransitiveDependencies: null, -); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef AudioHandlerInitRef = FutureProviderRef; -String _$playerHash() => r'9599b094cdd9eca614c27ec5bdf2d5259d20ac5f'; - -/// See also [Player]. -@ProviderFor(Player) -final playerProvider = NotifierProvider.internal( - Player.new, - name: r'playerProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$playerHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$Player = Notifier; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/features/player/providers/currently_playing_provider.dart b/lib/features/player/providers/currently_playing_provider.dart index 12bb78d..66ae515 100644 --- a/lib/features/player/providers/currently_playing_provider.dart +++ b/lib/features/player/providers/currently_playing_provider.dart @@ -1,13 +1,13 @@ -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'; +// 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'; diff --git a/lib/features/player/providers/player_status_provider.dart b/lib/features/player/providers/player_status_provider.dart index 2c24d6e..f193e53 100644 --- a/lib/features/player/providers/player_status_provider.dart +++ b/lib/features/player/providers/player_status_provider.dart @@ -1,40 +1,40 @@ -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:vaani/features/player/core/player_status.dart' as core; +// import 'package:riverpod_annotation/riverpod_annotation.dart'; +// import 'package:vaani/features/player/core/player_status.dart' as core; -part 'player_status_provider.g.dart'; +// part 'player_status_provider.g.dart'; -@Riverpod(keepAlive: true) -class PlayerStatus extends _$PlayerStatus { - @override - core.PlayerStatus build() { - return core.PlayerStatus(); - } +// @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 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 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 setPlayStatusVerify(core.PlayStatus playStatus) { +// if (state.playStatus != playStatus) { +// setPlayStatus(playStatus); +// } +// } - void setLoading(String itemId) { - state = state.copyWith( - playStatus: core.PlayStatus.loading, - itemId: itemId, - ); - } +// void setLoading(String itemId) { +// state = state.copyWith( +// playStatus: core.PlayStatus.loading, +// itemId: itemId, +// ); +// } - void setHidden() { - state = state.copyWith(playStatus: core.PlayStatus.hidden); - } -} +// void setHidden() { +// state = state.copyWith(playStatus: core.PlayStatus.hidden); +// } +// } diff --git a/lib/features/player/providers/player_status_provider.g.dart b/lib/features/player/providers/player_status_provider.g.dart deleted file mode 100644 index 5991a44..0000000 --- a/lib/features/player/providers/player_status_provider.g.dart +++ /dev/null @@ -1,25 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'player_status_provider.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$playerStatusHash() => r'4a8f222b8c1d5c92883f4358c69571c35a378861'; - -/// See also [PlayerStatus]. -@ProviderFor(PlayerStatus) -final playerStatusProvider = - NotifierProvider.internal( - PlayerStatus.new, - name: r'playerStatusProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') ? null : _$playerStatusHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$PlayerStatus = Notifier; -// ignore_for_file: type=lint -// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package diff --git a/lib/features/player/view/mini_player_bottom_padding.dart b/lib/features/player/view/mini_player_bottom_padding.dart index 996a70b..eaf7a12 100644 --- a/lib/features/player/view/mini_player_bottom_padding.dart +++ b/lib/features/player/view/mini_player_bottom_padding.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:vaani/features/player/providers/player_status_provider.dart'; +import 'package:vaani/features/player/providers/abs_provider.dart'; import 'package:vaani/globals.dart' show playerMinHeight; class MiniPlayerBottomPadding extends HookConsumerWidget { @@ -9,7 +9,7 @@ class MiniPlayerBottomPadding extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return AnimatedSize( duration: const Duration(milliseconds: 200), - child: ref.watch(playerStatusProvider).isPlaying() + child: ref.watch(absAudioPlayerProvider).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 095d6a7..3b4fa27 100644 --- a/lib/features/player/view/player_expanded_desktop.dart +++ b/lib/features/player/view/player_expanded_desktop.dart @@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shelfsdk/audiobookshelf_api.dart'; import 'package:vaani/constants/sizes.dart'; import 'package:vaani/features/player/providers/abs_provider.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; import 'package:vaani/features/player/view/player_expanded.dart' show PlayerExpandedImage; import 'package:vaani/features/player/view/player_minimized.dart'; @@ -201,7 +200,7 @@ class ChapterSelection extends HookConsumerWidget { selected: isCurrent, // key: isCurrent ? chapterKey : null, onTap: () { - ref.read(playerProvider).skipToChapter(chapter.id); + ref.read(absAudioPlayerProvider).switchChapter(chapter.id); }, ); }, diff --git a/lib/features/player/view/widgets/chapter_selection_button.dart b/lib/features/player/view/widgets/chapter_selection_button.dart index 6e934cd..3e1a975 100644 --- a/lib/features/player/view/widgets/chapter_selection_button.dart +++ b/lib/features/player/view/widgets/chapter_selection_button.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:vaani/features/player/providers/abs_provider.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; import 'package:vaani/features/player/view/player_expanded.dart' show pendingPlayerModals; import 'package:vaani/features/player/view/widgets/playing_indicator_icon.dart'; @@ -117,8 +116,8 @@ class ChapterSelectionModal extends HookConsumerWidget { onTap: () { Navigator.of(context).pop(); ref - .read(playerProvider) - .skipToChapter(chapter.id); + .read(absAudioPlayerProvider) + .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 f921375..03e4405 100644 --- a/lib/features/player/view/widgets/player_player_pause_button.dart +++ b/lib/features/player/view/widgets/player_player_pause_button.dart @@ -21,12 +21,12 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget { ); } - Widget _getIcon(PlayerState playerState, BuildContext context) { + Widget _getIcon(AbsPlayerState playerState, BuildContext context) { if (playerState.playing) { return Icon(size: iconSize, Icons.pause); } else { switch (playerState.processingState) { - case ProcessingState.loading || ProcessingState.buffering: + case AbsProcessingState.loading || AbsProcessingState.buffering: return CircularProgressIndicator(); default: return Icon(size: iconSize, Icons.play_arrow); @@ -34,13 +34,13 @@ class AudiobookPlayerPlayPauseButton extends HookConsumerWidget { } } - void _actionButtonPressed(PlayerState playerState, WidgetRef ref) async { + void _actionButtonPressed(AbsPlayerState playerState, WidgetRef ref) async { final player = ref.read(absAudioPlayerProvider); if (playerState.playing) { await player.pause(); } else { switch (playerState.processingState) { - case ProcessingState.completed: + case AbsProcessingState.completed: await player.seekInBook(const Duration(seconds: 0)); await player.play(); default: diff --git a/lib/features/player/view/widgets/player_progress_bar.dart b/lib/features/player/view/widgets/player_progress_bar.dart index 34823fe..1e5b254 100644 --- a/lib/features/player/view/widgets/player_progress_bar.dart +++ b/lib/features/player/view/widgets/player_progress_bar.dart @@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:vaani/constants/sizes.dart'; import 'package:vaani/features/player/providers/abs_provider.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; class AudiobookChapterProgressBar extends HookConsumerWidget { const AudiobookChapterProgressBar({ diff --git a/lib/features/player/view/widgets/speed_selector.dart b/lib/features/player/view/widgets/speed_selector.dart index 222a6b0..12a6966 100644 --- a/lib/features/player/view/widgets/speed_selector.dart +++ b/lib/features/player/view/widgets/speed_selector.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:list_wheel_scroll_view_nls/list_wheel_scroll_view_nls.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; +import 'package:vaani/features/player/providers/abs_provider.dart'; import 'package:vaani/features/settings/app_settings_provider.dart'; const double itemExtent = 25; @@ -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(playerProvider).player.speed; + final currentSpeed = ref.watch(absAudioPlayerProvider).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 ab16902..458ff17 100644 --- a/lib/features/shake_detector/shake_detector_provider.dart +++ b/lib/features/shake_detector/shake_detector_provider.dart @@ -2,12 +2,13 @@ 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/audiobook_player.dart'; -import 'package:vaani/features/sleep_timer/providers/sleep_timer_provider.dart' - show sleepTimerProvider; +import 'package:vaani/features/player/providers/abs_provider.dart'; 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:vibration/vibration.dart'; import 'shake_detector.dart' as core; @@ -31,14 +32,15 @@ class ShakeDetector extends _$ShakeDetector { } // if no book is loaded, shake detection should not be enabled - final player = ref.watch(playerProvider); + final player = ref.watch(absAudioPlayerProvider); player.playerStateStream.listen((event) { - if (event.processingState == ProcessingState.idle && wasPlayerLoaded) { + if (event.processingState == AbsProcessingState.idle && wasPlayerLoaded) { _logger.config('Player is now not loaded, invalidating'); wasPlayerLoaded = false; ref.invalidateSelf(); } - if (event.processingState != ProcessingState.idle && !wasPlayerLoaded) { + if (event.processingState != AbsProcessingState.idle && + !wasPlayerLoaded) { _logger.config('Player is now loaded, invalidating'); wasPlayerLoaded = true; ref.invalidateSelf(); @@ -86,7 +88,7 @@ class ShakeDetector extends _$ShakeDetector { ShakeAction shakeAction, { required Ref ref, }) { - final player = ref.read(playerProvider); + final player = ref.read(absAudioPlayerProvider); if (player.book == null && shakeAction.isPlaybackManagementEnabled) { _logger.warning('No book is loaded'); return false; @@ -103,23 +105,23 @@ class ShakeDetector extends _$ShakeDetector { return true; case ShakeAction.fastForward: _logger.fine('Fast forwarding'); - if (!player.player.playerState.playing) { + if (!player.playing) { _logger.warning('Player is not playing'); return false; } - player.seek(player.player.position + const Duration(seconds: 30)); + player.seek(player.position + const Duration(seconds: 30)); return true; case ShakeAction.rewind: _logger.fine('Rewinding'); - if (!player.player.playerState.playing) { + if (!player.playing) { _logger.warning('Player is not playing'); return false; } - player.seek(player.player.position - const Duration(seconds: 30)); + player.seek(player.position - const Duration(seconds: 30)); return true; case ShakeAction.playPause: _logger.fine('Toggling play/pause'); - player.togglePlayPause(); + player.playOrPause(); return true; default: 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 5bc8440..76a2526 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'b63082b9016958e6c1e46ff874c98a0c99721f04'; +String _$shakeDetectorHash() => r'ea00d8c830ef67f968bee25e0f924a35dc3e4cbf'; /// 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 c158923..1a3a75d 100644 --- a/lib/features/skip_start_end/core/skip_start_end.dart +++ b/lib/features/skip_start_end/core/skip_start_end.dart @@ -1,13 +1,13 @@ import 'dart:async'; -import 'package:vaani/features/player/core/audiobook_player.dart'; +import 'package:vaani/shared/audio_player.dart'; import 'package:vaani/shared/extensions/chapter.dart'; import 'package:vaani/shared/utils/throttler.dart'; class SkipStartEnd { final Duration start; final Duration end; - final AbsAudioHandler player; + final AbsAudioPlayer player; final List _subscriptions = []; final throttlerStart = Throttler(delay: Duration(seconds: 3)); @@ -33,12 +33,12 @@ class SkipStartEnd { } if (end > Duration.zero) { _subscriptions.add( - player.positionStreamInChapter.listen((positionChapter) { + player.positionInChapterStream.listen((positionChapter) { if (end > (player.currentChapter?.duration ?? Duration.zero) - positionChapter) { Future.microtask( - () => throttlerEnd.call(() => player.skipToNext()), + () => throttlerEnd.call(() => player.next()), ); } }), 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 f05a51b..18dba12 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 @@ -1,7 +1,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart'; import 'package:vaani/features/player/providers/abs_provider.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; import 'package:vaani/features/skip_start_end/core/skip_start_end.dart' as core; part 'skip_start_end_provider.g.dart'; @@ -16,7 +15,7 @@ class SkipStartEnd extends _$SkipStartEnd { return null; } - final player = ref.read(playerProvider); + final player = ref.read(absAudioPlayerProvider); 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 cffc000..0827087 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'ba92dd22fc76f04cb5aaa220d025eb69c9d2ba46'; +String _$skipStartEndHash() => r'b55dff90ed4ba467e9320d6bc081721336975bdb'; /// 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 e57f0d2..e746002 100644 --- a/lib/features/sleep_timer/core/sleep_timer.dart +++ b/lib/features/sleep_timer/core/sleep_timer.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'package:flutter_animate/flutter_animate.dart'; -import 'package:just_audio/just_audio.dart'; import 'package:logging/logging.dart'; +import 'package:vaani/shared/audio_player.dart'; /// this timer pauses the music player after a certain duration /// @@ -32,7 +32,7 @@ class SleepTimer { } /// The player to be paused - final AudioPlayer player; + final AbsAudioPlayer player; /// The timer that will pause the player Timer? timer; @@ -49,9 +49,9 @@ class SleepTimer { SleepTimer({required duration, required this.player}) : _duration = duration { _subscriptions.add( - player.playbackEventStream.listen((event) { - if (event.processingState == ProcessingState.completed || - event.processingState == ProcessingState.idle) { + player.playerStateStream.listen((event) { + if (event.processingState == AbsProcessingState.completed || + event.processingState == AbsProcessingState.idle) { clearCountDownTimer(); } }), diff --git a/lib/features/sleep_timer/providers/sleep_timer_provider.dart b/lib/features/sleep_timer/providers/sleep_timer_provider.dart index 3b84bc7..e931478 100644 --- a/lib/features/sleep_timer/providers/sleep_timer_provider.dart +++ b/lib/features/sleep_timer/providers/sleep_timer_provider.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:vaani/features/player/providers/audiobook_player.dart'; +import 'package:vaani/features/player/providers/abs_provider.dart'; import 'package:vaani/features/sleep_timer/core/sleep_timer.dart' as core; import 'package:vaani/features/settings/app_settings_provider.dart'; import 'package:vaani/shared/extensions/time_of_day.dart'; @@ -26,7 +26,7 @@ class SleepTimer extends _$SleepTimer { var sleepTimer = core.SleepTimer( duration: sleepTimerSettings.defaultDuration, - player: ref.watch(playerProvider).player, + player: ref.watch(absAudioPlayerProvider), ); ref.onDispose(sleepTimer.dispose); return sleepTimer; @@ -45,7 +45,7 @@ class SleepTimer extends _$SleepTimer { } else { final timer = core.SleepTimer( duration: resultingDuration, - player: ref.watch(playerProvider).player, + player: ref.watch(absAudioPlayerProvider), ); 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 a1471ba..2a52aee 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'daaaf63d599fb991e71a0da0ca1075fb46ccc6be'; +String _$sleepTimerHash() => r'8d91e2f7563320985ff5693b539288a93ff0d243'; /// See also [SleepTimer]. @ProviderFor(SleepTimer) diff --git a/lib/shared/audio_player.dart b/lib/shared/audio_player.dart index 5df416b..d8dce4e 100644 --- a/lib/shared/audio_player.dart +++ b/lib/shared/audio_player.dart @@ -20,15 +20,15 @@ final _logger = Logger('AbsAudioPlayer'); abstract class AbsAudioPlayer { final _mediaItemController = BehaviorSubject.seeded(null); final playerStateSubject = - BehaviorSubject.seeded(PlayerState(false, ProcessingState.idle)); + 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; - PlayerState get playerState => playerStateSubject.value; + AbsPlayerState get playerState => playerStateSubject.value; Stream get mediaItemStream => _mediaItemController.stream; - Stream get playerStateStream => playerStateSubject.stream; + Stream get playerStateStream => playerStateSubject.stream; Future load( BookExpanded book, { @@ -71,7 +71,7 @@ abstract class AbsAudioPlayer { baseUrl: baseUrl, token: token), ) .toList(); - setPlayList(playlist, index: indexTrack, position: positionInTrack); + await setPlayList(playlist, index: indexTrack, position: positionInTrack); } Future setPlayList( @@ -138,6 +138,7 @@ abstract class AbsAudioPlayer { await seekInBook(chapter.start + offset); } + bool get playing => playerState.playing; Stream get playingStream; Stream get bookStream => _bookStreamController.stream; Stream get chapterStream => _chapterStreamController.stream; @@ -185,7 +186,7 @@ abstract class AbsAudioPlayer { } /// Enumerates the different processing states of a player. -enum ProcessingState { +enum AbsProcessingState { /// The player has not loaded an [AudioSource]. idle, @@ -206,15 +207,15 @@ enum ProcessingState { /// 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 PlayerState { +class AbsPlayerState { /// Whether the player will play when [processingState] is /// [ProcessingState.ready]. final bool playing; /// The current processing state of the player. - final ProcessingState processingState; + final AbsProcessingState processingState; - PlayerState(this.playing, this.processingState); + AbsPlayerState(this.playing, this.processingState); @override String toString() => 'playing=$playing,processingState=$processingState'; @@ -229,11 +230,11 @@ class PlayerState { other.playing == playing && other.processingState == processingState; - PlayerState copyWith({ + AbsPlayerState copyWith({ bool? playing, - ProcessingState? processingState, + AbsProcessingState? processingState, }) { - return PlayerState( + return AbsPlayerState( playing ?? this.playing, processingState ?? this.processingState, ); diff --git a/lib/shared/audio_player_mpv.dart b/lib/shared/audio_player_mpv.dart index e19418c..b442e54 100644 --- a/lib/shared/audio_player_mpv.dart +++ b/lib/shared/audio_player_mpv.dart @@ -12,20 +12,23 @@ class AbsMpvAudioPlayer extends AbsAudioPlayer { state.copyWith( playing: playing, processingState: playing - ? state.processingState == ProcessingState.idle - ? ProcessingState.ready + ? state.processingState == AbsProcessingState.idle + ? AbsProcessingState.ready : state.processingState : player.state.buffering - ? ProcessingState.buffering + ? AbsProcessingState.buffering : player.state.completed - ? ProcessingState.completed - : ProcessingState.ready, + ? AbsProcessingState.completed + : AbsProcessingState.ready, ), ); }); } @override - Stream get bufferedPositionInBookStream => player.stream.buffer; + Duration get bufferedPosition => player.state.buffer; + + @override + Stream get bufferedPositionStream => player.stream.buffer; @override int get currentIndex => player.state.playlist.index; @@ -45,6 +48,9 @@ class AbsMpvAudioPlayer extends AbsAudioPlayer { await player.playOrPause(); } + @override + Stream get playingStream => player.stream.playing; + @override Duration get position => player.state.position; @@ -91,17 +97,5 @@ class AbsMpvAudioPlayer extends AbsAudioPlayer { } @override - Stream get playingStream => player.stream.playing; - - @override - // TODO: implement speed double get speed => player.state.rate; - - @override - // TODO: implement bufferedPosition - Duration get bufferedPosition => player.state.buffer; - - @override - // TODO: implement bufferedPositionStream - Stream get bufferedPositionStream => player.stream.buffer; } diff --git a/lib/shared/audio_player_platform.dart b/lib/shared/audio_player_platform.dart new file mode 100644 index 0000000..7720e53 --- /dev/null +++ b/lib/shared/audio_player_platform.dart @@ -0,0 +1,96 @@ +import 'package:just_audio/just_audio.dart'; +import 'package:logging/logging.dart'; +import 'package:vaani/shared/audio_player.dart'; + +final _logger = Logger('AbsPlatformAudioPlayer'); + +class AbsPlatformAudioPlayer extends AbsAudioPlayer { + final AudioPlayer player = AudioPlayer(); + AbsPlatformAudioPlayer() { + player.playerStateStream.listen((state) { + playerStateSubject.add( + playerState.copyWith( + playing: state.playing, + processingState: { + ProcessingState.idle: AbsProcessingState.idle, + ProcessingState.buffering: AbsProcessingState.buffering, + ProcessingState.completed: AbsProcessingState.completed, + ProcessingState.loading: AbsProcessingState.loading, + ProcessingState.ready: AbsProcessingState.ready, + }[state.processingState], + ), + ); + }); + } + @override + Duration get bufferedPosition => player.bufferedPosition; + + @override + Stream get bufferedPositionStream => player.bufferedPositionStream; + + @override + int get currentIndex => player.currentIndex ?? 0; + + @override + Future pause() async { + await player.pause(); + } + + @override + Future play() async { + await player.play(); + } + + @override + Future playOrPause() async { + player.playing ? await player.pause() : await player.play(); + } + + @override + Stream get playingStream => player.playingStream; + + @override + Duration get position => player.position; + + @override + Stream get positionStream => player.positionStream; + + @override + Future seek(Duration position, {int? index}) async { + await player.seek(position, index: index); + } + + @override + Future setPlayList( + List playlist, { + int? index, + Duration? position, + }) async { + List audioSources = + playlist.map((uri) => AudioSource.uri(uri)).toList(); + await player + .setAudioSources( + audioSources, + preload: true, + initialIndex: index, + initialPosition: position, + ) + .catchError((error) { + _logger.shout('Error in setting audio source: $error'); + return null; + }); + } + + @override + Future setSpeed(double speed) async { + await player.setSpeed(speed); + } + + @override + Future setVolume(double volume) async { + await player.setVolume(volume); + } + + @override + double get speed => player.speed; +}