优化一下通知栏显示

This commit is contained in:
rang 2025-12-07 17:55:07 +08:00
parent d6894c3191
commit f4f860f3ec
12 changed files with 133 additions and 69 deletions

View file

@ -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;
}

View file

@ -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,
);
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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)

View file

@ -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,
),