From f4f860f3ec8b1ad415aa70dbd19cc2122ad6218c Mon Sep 17 00:00:00 2001 From: rang <378694192@qq.com> Date: Sun, 7 Dec 2025 17:55:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=80=E4=B8=8B=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E6=A0=8F=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 6 +++ ios/Runner.xcodeproj/project.pbxproj | 6 ++- ios/Runner/Info.plist | 26 ++++++------- .../player/core/abs_audio_handler.dart | 37 +++++++++++++++++-- .../player/core/abs_audio_player.dart | 4 ++ lib/features/player/core/init.dart | 30 --------------- .../player/providers/abs_provider.dart | 30 ++++++++++++--- .../player/providers/abs_provider.g.dart | 21 ++++++++++- .../player/view/player_minimized.dart | 18 ++++++--- lib/router/scaffold_with_nav_bar.dart | 4 +- lib/shared/widgets/shelves/book_shelf.dart | 14 +++---- macos/Podfile.lock | 6 +++ 12 files changed, 133 insertions(+), 69 deletions(-) delete mode 100644 lib/features/player/core/init.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 8116a7d..22ff5ac 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -48,6 +48,8 @@ PODS: - just_audio (0.0.1): - Flutter - FlutterMacOS + - media_kit_libs_ios_audio (1.0.4): + - Flutter - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -80,6 +82,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`) - just_audio (from `.symlinks/plugins/just_audio/darwin`) + - media_kit_libs_ios_audio (from `.symlinks/plugins/media_kit_libs_ios_audio/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) @@ -113,6 +116,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/isar_flutter_libs/ios" just_audio: :path: ".symlinks/plugins/just_audio/darwin" + media_kit_libs_ios_audio: + :path: ".symlinks/plugins/media_kit_libs_ios_audio/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -141,6 +146,7 @@ SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79 + media_kit_libs_ios_audio: 8f39d96a9c630685dfb844c289bd1d114c486fb3 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e383518..f7b57d4 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -105,7 +105,6 @@ 3E1CA9493996109C19FFDD1A /* Pods-RunnerTests.release.xcconfig */, 9830F7B941AA345666516454 /* Pods-RunnerTests.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -492,6 +491,7 @@ DEVELOPMENT_TEAM = UV4P38S7RL; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -675,11 +675,12 @@ DEVELOPMENT_TEAM = UV4P38S7RL; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = dr.blank.vaani; + PRODUCT_BUNDLE_IDENTIFIER = dr.blank.vaani.debug; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -698,6 +699,7 @@ DEVELOPMENT_TEAM = UV4P38S7RL; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 21fb584..5648041 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + CADisableMinimumFrameDurationOnPhone + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName @@ -24,6 +26,17 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIApplicationSupportsIndirectInputEvents + + UIBackgroundModes + + audio + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -41,18 +54,5 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - CADisableMinimumFrameDurationOnPhone - - UIApplicationSupportsIndirectInputEvents - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - - UIBackgroundModes - - audio - diff --git a/lib/features/player/core/abs_audio_handler.dart b/lib/features/player/core/abs_audio_handler.dart index 7119ab9..7167f07 100644 --- a/lib/features/player/core/abs_audio_handler.dart +++ b/lib/features/player/core/abs_audio_handler.dart @@ -1,18 +1,45 @@ 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'; class AbsAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { final Player player = Player(); - AbsAudioHandler() { + AbsAudioHandler(Ref ref) { + playbackState.add( + playbackState.value.copyWith( + controls: [ + MediaControl.skipToPrevious, + if (player.state.playing) MediaControl.pause else MediaControl.play, + // MediaControl.rewind, + // MediaControl.fastForward, + MediaControl.skipToNext, + MediaControl.stop, + ], + systemActions: { + MediaAction.play, + MediaAction.pause, + MediaAction.seek, + MediaAction.seekForward, + MediaAction.seekBackward, + }, + ), + ); + + final absState = ref.read(absStateProvider.notifier); // 1. 转发播放/暂停状态 player.stream.playing.listen((bool playing) { playbackState.add(playbackState.value.copyWith( playing: playing, // 根据 playing 和实际情况更新 processingState - processingState: - playing ? AudioProcessingState.ready : AudioProcessingState.idle, + processingState: player.state.completed + ? AudioProcessingState.completed + : player.state.buffering + ? AudioProcessingState.buffering + : AudioProcessingState.ready, )); + absState.updataPlaying(playing); }); // 2. 转发播放位置 player.stream.position.listen((Duration position) { @@ -76,6 +103,10 @@ class AbsAudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler { } Future setVolume(double volume) async { + final state = player.state; await player.setVolume(volume); } + + PlayerStream get stream => player.stream; + PlayerState get state => player.state; } diff --git a/lib/features/player/core/abs_audio_player.dart b/lib/features/player/core/abs_audio_player.dart index a3ca26e..14ee0fe 100644 --- a/lib/features/player/core/abs_audio_player.dart +++ b/lib/features/player/core/abs_audio_player.dart @@ -8,22 +8,26 @@ class AbsPlayerState { final api.BookChapter? currentChapter; // 当前音轨序号 final int currentIndex; + final bool playing; 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, ); } } diff --git a/lib/features/player/core/init.dart b/lib/features/player/core/init.dart deleted file mode 100644 index 9335d70..0000000 --- a/lib/features/player/core/init.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:audio_service/audio_service.dart'; -import 'package:audio_session/audio_session.dart'; -import 'package:media_kit/media_kit.dart'; -import 'package:vaani/features/player/core/abs_audio_handler.dart' as core; - -Future absAudioHandlerInit() 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()); - - final audioService = await AudioService.init( - builder: () => core.AbsAudioHandler(), - 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; -} diff --git a/lib/features/player/providers/abs_provider.dart b/lib/features/player/providers/abs_provider.dart index 79cde6c..ca52abc 100644 --- a/lib/features/player/providers/abs_provider.dart +++ b/lib/features/player/providers/abs_provider.dart @@ -30,7 +30,7 @@ Future absAudioHandlerInit(Ref ref) async { await session.configure(const AudioSessionConfiguration.speech()); final audioService = await AudioService.init( - builder: () => core.AbsAudioHandler(), + builder: () => core.AbsAudioHandler(ref), config: const AudioServiceConfig( androidNotificationChannelId: 'dr.blank.vaani.channel.audio', androidNotificationChannelName: 'ABSPlayback', @@ -91,9 +91,9 @@ class AbsState extends _$AbsState { final initialIndex = book.tracks.indexOf(trackToPlay); final initialPositionInTrack = currentTime != null ? currentTime - trackToPlay.startOffset : null; - final title = appSettings.notificationSettings.primaryTitle + final album = appSettings.notificationSettings.primaryTitle .formatNotificationTitle(book); - final album = appSettings.notificationSettings.secondaryTitle + final artlist = appSettings.notificationSettings.secondaryTitle .formatNotificationTitle(book); final id = _getUri(trackToPlay, downloadedUris, @@ -101,10 +101,9 @@ class AbsState extends _$AbsState { .toString(); final item = MediaItem( id: id, - title: title, + title: chapterToPlay.title, album: album, - displayTitle: title, - displaySubtitle: album, + artist: artlist, duration: chapterToPlay.duration, artUri: Uri.parse( '${api.baseUrl}/api/items/${book.libraryItemId}/cover?token=${api.token!}', @@ -154,6 +153,20 @@ class AbsState extends _$AbsState { Future next() async {} Future previous() async {} + void updataPlaying(bool playing) { + state = state.copyWith(playing: playing); + } + + Stream get positionStreamInChapter { + final player = ref.read(absPlayerProvider); + + return player.stream.position.distinct().map((position) { + return position + + (state.book?.tracks[state.currentIndex].startOffset ?? + Duration.zero) - + (state.currentChapter?.start ?? Duration.zero); + }); + } Uri _getUri( api.AudioTrack track, @@ -172,3 +185,8 @@ class AbsState extends _$AbsState { Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token'); } } + +@riverpod +Stream positionChapter(Ref ref) { + return ref.watch(absStateProvider.notifier).positionStreamInChapter; +} diff --git a/lib/features/player/providers/abs_provider.g.dart b/lib/features/player/providers/abs_provider.g.dart index a51b571..aaa0409 100644 --- a/lib/features/player/providers/abs_provider.g.dart +++ b/lib/features/player/providers/abs_provider.g.dart @@ -7,7 +7,7 @@ part of 'abs_provider.dart'; // ************************************************************************** String _$absAudioHandlerInitHash() => - r'8815383b114e5e3da826afdea58bf0a884b1e3f2'; + r'bb46f715e9d51bb6269d0d77e314665601a6bdb0'; /// See also [absAudioHandlerInit]. @ProviderFor(absAudioHandlerInit) @@ -25,6 +25,23 @@ final absAudioHandlerInitProvider = @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef AbsAudioHandlerInitRef = FutureProviderRef; +String _$positionChapterHash() => r'b1d19345bceb2e54399e15fbb16a534f4be5ba43'; + +/// See also [positionChapter]. +@ProviderFor(positionChapter) +final positionChapterProvider = AutoDisposeStreamProvider.internal( + positionChapter, + name: r'positionChapterProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$positionChapterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +@Deprecated('Will be removed in 3.0. Use Ref instead') +// ignore: unused_element +typedef PositionChapterRef = AutoDisposeStreamProviderRef; String _$absPlayerHash() => r'c313a2456bb221b83f3cd2142ae63d6463ef304b'; /// See also [AbsPlayer]. @@ -40,7 +57,7 @@ final absPlayerProvider = ); typedef _$AbsPlayer = Notifier; -String _$absStateHash() => r'fb11d9d970e0de2dfd722c1f0de2a3b9b10f2859'; +String _$absStateHash() => r'6b4ca07c7998304a1522a07b23955c3e54a441e3'; /// See also [AbsState]. @ProviderFor(AbsState) diff --git a/lib/features/player/view/player_minimized.dart b/lib/features/player/view/player_minimized.dart index eae9cd5..fc8a925 100644 --- a/lib/features/player/view/player_minimized.dart +++ b/lib/features/player/view/player_minimized.dart @@ -4,10 +4,12 @@ import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:go_router/go_router.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'; import 'package:vaani/features/player/providers/currently_playing_provider.dart'; import 'package:vaani/features/player/view/widgets/player_player_pause_button.dart'; import 'package:vaani/router/router.dart'; +import 'package:vaani/shared/extensions/chapter.dart'; import 'package:vaani/shared/extensions/model_conversions.dart'; import 'package:vaani/shared/widgets/shelves/book_shelf.dart'; @@ -19,11 +21,12 @@ class PlayerMinimized extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final currentBook = ref.watch(currentBookProvider); + final currentBook = ref.watch(absStateProvider.select((v) => v.book)); if (currentBook == null) { return SizedBox.shrink(); } - final currentChapter = ref.watch(currentChapterProvider); + final currentChapter = + ref.watch(absStateProvider.select((v) => v.currentChapter)); return PlayerMinimizedFramework( children: [ @@ -112,9 +115,14 @@ class PlayerMinimizedFramework extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final player = ref.watch(playerProvider); + // final player = ref.watch(playerProvider); + final currentChapter = + ref.watch(absStateProvider.select((v) => v.currentChapter)); + final progress = - useStream(player.positionStreamInChapter, initialData: Duration.zero); + // useStream(player.positionStreamInChapter, initialData: Duration.zero); + useStream(ref.read(absStateProvider.notifier).positionStreamInChapter, + initialData: Duration.zero); return GestureDetector( onTap: () { if (GoRouterState.of(context).topRoute?.name != Routes.player.name) { @@ -136,7 +144,7 @@ class PlayerMinimizedFramework extends HookConsumerWidget { height: AppElementSizes.barHeight, child: LinearProgressIndicator( value: (progress.data ?? Duration.zero).inSeconds / - (player.chapterDuration?.inSeconds ?? 1), + (currentChapter?.duration.inSeconds ?? 1), // color: Theme.of(context).colorScheme.onPrimaryContainer, // backgroundColor: Theme.of(context).colorScheme.primaryContainer, ), diff --git a/lib/router/scaffold_with_nav_bar.dart b/lib/router/scaffold_with_nav_bar.dart index 8f4187e..e1c964a 100644 --- a/lib/router/scaffold_with_nav_bar.dart +++ b/lib/router/scaffold_with_nav_bar.dart @@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:vaani/api/library_provider.dart' show currentLibraryProvider; import 'package:vaani/features/explore/providers/search_controller.dart'; +import 'package:vaani/features/player/providers/abs_provider.dart'; import 'package:vaani/features/player/providers/currently_playing_provider.dart'; import 'package:vaani/features/player/view/player_minimized.dart'; import 'package:vaani/features/you/view/widgets/library_switch_chip.dart'; @@ -50,7 +51,8 @@ class ScaffoldWithNavBar extends HookConsumerWidget { Widget buildNavLeft(BuildContext context, WidgetRef ref) { // final isPlayerActive = ref.watch(isPlayerActiveProvider); - final currentBook = ref.watch(currentBookProvider); + // final currentBook = ref.watch(currentBookProvider); + final currentBook = ref.watch(absStateProvider.select((v) => v.book)); return Padding( padding: EdgeInsets.only(bottom: currentBook != null ? playerMinHeight : 0), diff --git a/lib/shared/widgets/shelves/book_shelf.dart b/lib/shared/widgets/shelves/book_shelf.dart index 2e49d11..52d3680 100644 --- a/lib/shared/widgets/shelves/book_shelf.dart +++ b/lib/shared/widgets/shelves/book_shelf.dart @@ -216,13 +216,13 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final me = ref.watch(meProvider); // final player = ref.watch(audiobookPlayerProvider); - final currentBook = ref.watch(currentBookProvider); - final playerStatus = ref.watch(playerStatusProvider); - final isLoading = playerStatus.isLoading(libraryItemId); + final currentBook = ref.watch(absStateProvider.select((v) => v.book)); + final playing = ref.watch(absStateProvider.select((v) => v.playing)); + // final playerStatus = ref.watch(playerStatusProvider); + // final isLoading = playerStatus.isLoading(libraryItemId); final isCurrentBookSetInPlayer = currentBook?.libraryItemId == libraryItemId; - final isPlayingThisBook = - playerStatus.isPlaying() && isCurrentBookSetInPlayer; + final isPlayingThisBook = playing && isCurrentBookSetInPlayer; final userProgress = me.valueOrNull?.mediaProgress ?.firstWhereOrNull((element) => element.libraryItemId == libraryItemId); @@ -308,7 +308,7 @@ class _BookOnShelfPlayButton extends HookConsumerWidget { icon: Hero( tag: HeroTagPrefixes.libraryItemPlayButton + libraryItemId, child: DynamicItemPlayIcon( - isLoading: isLoading, + // isLoading: isLoading, isBookCompleted: isBookCompleted, isPlayingThisBook: isPlayingThisBook, isCurrentBookSetInPlayer: isCurrentBookSetInPlayer, @@ -355,7 +355,7 @@ class BookCoverWidget extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final currentBook = ref.watch(currentBookProvider); + final currentBook = ref.watch(absStateProvider.select((v) => v.book)); if (currentBook == null) { return const BookCoverSkeleton(); } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 72935a4..d435d71 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -16,6 +16,8 @@ PODS: - just_audio (0.0.1): - Flutter - FlutterMacOS + - media_kit_libs_macos_audio (1.0.4): + - FlutterMacOS - package_info_plus (0.0.1): - FlutterMacOS - path_provider_foundation (0.0.1): @@ -44,6 +46,7 @@ DEPENDENCIES: - FlutterMacOS (from `Flutter/ephemeral`) - isar_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos`) - just_audio (from `Flutter/ephemeral/.symlinks/plugins/just_audio/darwin`) + - media_kit_libs_macos_audio (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) @@ -70,6 +73,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos just_audio: :path: Flutter/ephemeral/.symlinks/plugins/just_audio/darwin + media_kit_libs_macos_audio: + :path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos package_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: @@ -96,6 +101,7 @@ SPEC CHECKSUMS: FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 isar_flutter_libs: 43385c99864c168fadba7c9adeddc5d38838ca6a just_audio: a42c63806f16995daf5b219ae1d679deb76e6a79 + media_kit_libs_macos_audio: 3871782a4f3f84c77f04d7666c87800a781c24da package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161