player: add chapter skip buttons

This commit is contained in:
Dr-Blank 2024-05-19 09:45:41 -04:00
parent 6557e63a28
commit 36509913f2
No known key found for this signature in database
GPG key ID: 7452CC63F210A266
4 changed files with 58 additions and 53 deletions

View file

@ -22,14 +22,21 @@ Duration sumOfTracks(BookExpanded book, int? index) {
/// returns the [AudioTrack] to play based on the [position] in the [book] /// returns the [AudioTrack] to play based on the [position] in the [book]
AudioTrack getTrackToPlay(BookExpanded book, Duration position) { AudioTrack getTrackToPlay(BookExpanded book, Duration position) {
var totalDuration = Duration.zero; // var totalDuration = Duration.zero;
for (var track in book.tracks) { // for (var track in book.tracks) {
totalDuration += track.duration; // totalDuration += track.duration;
if (totalDuration >= position) { // if (totalDuration >= position) {
return track; // return track;
} // }
} // }
return book.tracks.last; // return book.tracks.last;
return book.tracks.firstWhere(
(element) {
return element.startOffset <= position &&
(element.startOffset + element.duration) >= position;
},
orElse: () => book.tracks.last,
);
} }
/// will manage the audio player instance /// will manage the audio player instance
@ -90,11 +97,11 @@ class AudiobookPlayer extends AudioPlayer {
// hence first we need to calculate the current track which will be used to set the initial position // hence first we need to calculate the current track which will be used to set the initial position
// then we set the initial index to the current track index and position as the remaining duration from the position // then we set the initial index to the current track index and position as the remaining duration from the position
// after subtracting the duration of all the previous tracks // after subtracting the duration of all the previous tracks
// initialPosition ;
final trackToPlay = getTrackToPlay(book, initialPosition ?? Duration.zero); final trackToPlay = getTrackToPlay(book, initialPosition ?? Duration.zero);
final initialIndex = book.tracks.indexOf(trackToPlay); final initialIndex = book.tracks.indexOf(trackToPlay);
final initialPositionInTrack = initialPosition != null final initialPositionInTrack = initialPosition != null
? initialPosition - sumOfTracks(book, initialIndex - 1) ? initialPosition - trackToPlay.startOffset
: null; : null;
await setAudioSource( await setAudioSource(
@ -131,7 +138,7 @@ class AudiobookPlayer extends AudioPlayer {
throw StateError('No book is set'); throw StateError('No book is set');
} }
// TODO refactor this // TODO refactor this to cover all the states
return switch (playerState) { return switch (playerState) {
PlayerState(playing: var isPlaying) => isPlaying ? pause() : play(), PlayerState(playing: var isPlaying) => isPlaying ? pause() : play(),
}; };
@ -142,30 +149,48 @@ class AudiobookPlayer extends AudioPlayer {
/// so we need to calculate the duration and current position based on the book /// so we need to calculate the duration and current position based on the book
@override @override
Future<void> seek(Duration? position, {int? index}) async { Future<void> seek(Duration? positionInBook, {int? index}) async {
if (_book == null) { if (_book == null) {
return; return;
} }
if (position == null) { if (positionInBook == null) {
return; return;
} }
final trackToPlay = getTrackToPlay(_book!, position); final tracks = _book!.tracks;
final index = _book!.tracks.indexOf(trackToPlay); final trackToPlay = getTrackToPlay(_book!, positionInBook);
final positionInTrack = position - sumOfTracks(_book!, index - 1); final index = tracks.indexOf(trackToPlay);
final positionInTrack = positionInBook - trackToPlay.startOffset;
return super.seek(positionInTrack, index: index); return super.seek(positionInTrack, index: index);
} }
/// a convenience method to get position in the book instead of the current track position
Duration get positionInBook {
if (_book == null) {
return Duration.zero;
}
return position + _book!.tracks[sequenceState!.currentIndex].startOffset;
}
/// a convenience method to get the buffered position in the book instead of the current track position
Duration get bufferedPositionInBook {
if (_book == null) {
return Duration.zero;
}
return bufferedPosition + _book!.tracks[sequenceState!.currentIndex].startOffset;
}
/// streams to override to suit the book instead of the current track /// streams to override to suit the book instead of the current track
// - positionStream // - positionStream
// - bufferedPositionStream // - bufferedPositionStream
@override @override
Stream<Duration> get positionStream { Stream<Duration> get positionStream {
// return the positioninbook stream
return super.positionStream.map((position) { return super.positionStream.map((position) {
if (_book == null) { if (_book == null) {
return Duration.zero; return Duration.zero;
} }
return position + sumOfTracks(_book!, sequenceState!.currentIndex); return position + _book!.tracks[sequenceState!.currentIndex].startOffset;
}); });
} }
@ -175,7 +200,7 @@ class AudiobookPlayer extends AudioPlayer {
if (_book == null) { if (_book == null) {
return Duration.zero; return Duration.zero;
} }
return position + sumOfTracks(_book!, sequenceState!.currentIndex); return position + _book!.tracks[sequenceState!.currentIndex].startOffset;
}); });
} }
@ -186,12 +211,9 @@ class AudiobookPlayer extends AudioPlayer {
} }
return _book!.chapters.firstWhere( return _book!.chapters.firstWhere(
(element) { (element) {
return element.start <= position && element.end >= position; return element.start <= positionInBook && element.end >= positionInBook;
}, },
orElse: () => _book!.chapters.first, orElse: () => _book!.chapters.first,
); );
} }
} }

View file

@ -14,13 +14,7 @@ BookExpanded? currentlyPlayingBook(CurrentlyPlayingBookRef ref) {
@riverpod @riverpod
BookChapter? currentPlayingChapter(CurrentPlayingChapterRef ref) { BookChapter? currentPlayingChapter(CurrentPlayingChapterRef ref) {
final player = ref.watch(audiobookPlayerProvider); final player = ref.watch(audiobookPlayerProvider);
// get the current timestamp return player.currentChapter;
final currentTimestamp = player.position;
// get the chapter that contains the current timestamp
return player.book?.chapters.firstWhere(
(element) =>
element.start <= currentTimestamp && element.end >= currentTimestamp,
);
} }
/// provides the book metadata of the currently playing book /// provides the book metadata of the currently playing book

View file

@ -24,7 +24,7 @@ final currentlyPlayingBookProvider =
typedef CurrentlyPlayingBookRef = AutoDisposeProviderRef<BookExpanded?>; typedef CurrentlyPlayingBookRef = AutoDisposeProviderRef<BookExpanded?>;
String _$currentPlayingChapterHash() => String _$currentPlayingChapterHash() =>
r'562416b7e0068aaba9138cb8e0ed7a5ddba8e6c6'; r'3a621260211cddecfd974b31d5c4820ed24b1545';
/// provided the current chapter of the book being played /// provided the current chapter of the book being played
/// ///

View file

@ -1,5 +1,4 @@
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -270,6 +269,7 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(audiobookPlayerProvider); final player = ref.watch(audiobookPlayerProvider);
final currentChapter = ref.watch(currentPlayingChapterProvider);
final position = useStream( final position = useStream(
player.positionStream, player.positionStream,
initialData: const Duration(seconds: 0), initialData: const Duration(seconds: 0),
@ -278,34 +278,22 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
player.bufferedPositionStream, player.bufferedPositionStream,
initialData: const Duration(seconds: 0), initialData: const Duration(seconds: 0),
); );
final currentIndex = useStream(
player.currentIndexStream,
initialData: 0,
);
final durationOfPreviousTracks =
player.book?.tracks.sublist(0, currentIndex.data).fold(
const Duration(seconds: 0),
(previousValue, element) => previousValue + element.duration,
) ??
const Duration(seconds: 0);
final totalProgress = durationOfPreviousTracks +
(position.data ?? const Duration(seconds: 0));
final totalBuffered = durationOfPreviousTracks +
(buffered.data ?? const Duration(seconds: 0));
// now find the chapter that corresponds to the current time // now find the chapter that corresponds to the current time
// and calculate the progress of the current chapter // and calculate the progress of the current chapter
final currentChapter = player.book?.chapters.firstWhereOrNull(
(element) =>
(element.start <= totalProgress) && (element.end >= totalProgress),
);
final currentChapterProgress = final currentChapterProgress =
currentChapter == null ? null : (totalProgress - currentChapter.start); currentChapter == null
? null
: (player.positionInBook - currentChapter.start);
final currentChapterBuffered = final currentChapterBuffered =
currentChapter == null ? null : (totalBuffered - currentChapter.start); currentChapter == null
? null
: (player.bufferedPositionInBook - currentChapter.start);
return ProgressBar( return ProgressBar(
progress: currentChapterProgress ?? totalProgress, progress:
currentChapterProgress ?? position.data ?? const Duration(seconds: 0),
total: currentChapter == null total: currentChapter == null
? player.book?.duration ?? const Duration(seconds: 0) ? player.book?.duration ?? const Duration(seconds: 0)
: currentChapter.end - currentChapter.start, : currentChapter.end - currentChapter.start,
@ -316,7 +304,8 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
); );
}, },
thumbRadius: 8, thumbRadius: 8,
buffered: currentChapterBuffered ?? totalBuffered, buffered:
currentChapterBuffered ?? buffered.data ?? const Duration(seconds: 0),
bufferedBarColor: Theme.of(context).colorScheme.secondary, bufferedBarColor: Theme.of(context).colorScheme.secondary,
timeLabelType: TimeLabelType.remainingTime, timeLabelType: TimeLabelType.remainingTime,
timeLabelLocation: TimeLabelLocation.below, timeLabelLocation: TimeLabelLocation.below,