mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-02-16 14:29:35 +00:00
优化一下通知栏显示
This commit is contained in:
parent
d6894c3191
commit
f4f860f3ec
12 changed files with 133 additions and 69 deletions
|
|
@ -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<void> setVolume(double volume) async {
|
||||
final state = player.state;
|
||||
await player.setVolume(volume);
|
||||
}
|
||||
|
||||
PlayerStream get stream => player.stream;
|
||||
PlayerState get state => player.state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<core.AbsAudioHandler> 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;
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ Future<core.AbsAudioHandler> 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<void> next() async {}
|
||||
|
||||
Future<void> previous() async {}
|
||||
void updataPlaying(bool playing) {
|
||||
state = state.copyWith(playing: playing);
|
||||
}
|
||||
|
||||
Stream<Duration> 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<Duration> positionChapter(Ref ref) {
|
||||
return ref.watch(absStateProvider.notifier).positionStreamInChapter;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<core.AbsAudioHandler>;
|
||||
String _$positionChapterHash() => r'b1d19345bceb2e54399e15fbb16a534f4be5ba43';
|
||||
|
||||
/// See also [positionChapter].
|
||||
@ProviderFor(positionChapter)
|
||||
final positionChapterProvider = AutoDisposeStreamProvider<Duration>.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<Duration>;
|
||||
String _$absPlayerHash() => r'c313a2456bb221b83f3cd2142ae63d6463ef304b';
|
||||
|
||||
/// See also [AbsPlayer].
|
||||
|
|
@ -40,7 +57,7 @@ final absPlayerProvider =
|
|||
);
|
||||
|
||||
typedef _$AbsPlayer = Notifier<core.AbsAudioHandler>;
|
||||
String _$absStateHash() => r'fb11d9d970e0de2dfd722c1f0de2a3b9b10f2859';
|
||||
String _$absStateHash() => r'6b4ca07c7998304a1522a07b23955c3e54a441e3';
|
||||
|
||||
/// See also [AbsState].
|
||||
@ProviderFor(AbsState)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue