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]
AudioTrack getTrackToPlay(BookExpanded book, Duration position) {
var totalDuration = Duration.zero;
for (var track in book.tracks) {
totalDuration += track.duration;
if (totalDuration >= position) {
return track;
}
}
return book.tracks.last;
// var totalDuration = Duration.zero;
// for (var track in book.tracks) {
// totalDuration += track.duration;
// if (totalDuration >= position) {
// return track;
// }
// }
// 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
@ -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
// 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
// initialPosition ;
final trackToPlay = getTrackToPlay(book, initialPosition ?? Duration.zero);
final initialIndex = book.tracks.indexOf(trackToPlay);
final initialPositionInTrack = initialPosition != null
? initialPosition - sumOfTracks(book, initialIndex - 1)
? initialPosition - trackToPlay.startOffset
: null;
await setAudioSource(
@ -131,7 +138,7 @@ class AudiobookPlayer extends AudioPlayer {
throw StateError('No book is set');
}
// TODO refactor this
// TODO refactor this to cover all the states
return switch (playerState) {
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
@override
Future<void> seek(Duration? position, {int? index}) async {
Future<void> seek(Duration? positionInBook, {int? index}) async {
if (_book == null) {
return;
}
if (position == null) {
if (positionInBook == null) {
return;
}
final trackToPlay = getTrackToPlay(_book!, position);
final index = _book!.tracks.indexOf(trackToPlay);
final positionInTrack = position - sumOfTracks(_book!, index - 1);
final tracks = _book!.tracks;
final trackToPlay = getTrackToPlay(_book!, positionInBook);
final index = tracks.indexOf(trackToPlay);
final positionInTrack = positionInBook - trackToPlay.startOffset;
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
// - positionStream
// - bufferedPositionStream
@override
Stream<Duration> get positionStream {
// return the positioninbook stream
return super.positionStream.map((position) {
if (_book == null) {
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) {
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(
(element) {
return element.start <= position && element.end >= position;
return element.start <= positionInBook && element.end >= positionInBook;
},
orElse: () => _book!.chapters.first,
);
}
}

View file

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

View file

@ -24,7 +24,7 @@ final currentlyPlayingBookProvider =
typedef CurrentlyPlayingBookRef = AutoDisposeProviderRef<BookExpanded?>;
String _$currentPlayingChapterHash() =>
r'562416b7e0068aaba9138cb8e0ed7a5ddba8e6c6';
r'3a621260211cddecfd974b31d5c4820ed24b1545';
/// 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:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -270,6 +269,7 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(audiobookPlayerProvider);
final currentChapter = ref.watch(currentPlayingChapterProvider);
final position = useStream(
player.positionStream,
initialData: const Duration(seconds: 0),
@ -278,34 +278,22 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
player.bufferedPositionStream,
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
// and calculate the progress of the current chapter
final currentChapter = player.book?.chapters.firstWhereOrNull(
(element) =>
(element.start <= totalProgress) && (element.end >= totalProgress),
);
final currentChapterProgress =
currentChapter == null ? null : (totalProgress - currentChapter.start);
currentChapter == null
? null
: (player.positionInBook - currentChapter.start);
final currentChapterBuffered =
currentChapter == null ? null : (totalBuffered - currentChapter.start);
currentChapter == null
? null
: (player.bufferedPositionInBook - currentChapter.start);
return ProgressBar(
progress: currentChapterProgress ?? totalProgress,
progress:
currentChapterProgress ?? position.data ?? const Duration(seconds: 0),
total: currentChapter == null
? player.book?.duration ?? const Duration(seconds: 0)
: currentChapter.end - currentChapter.start,
@ -316,7 +304,8 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
);
},
thumbRadius: 8,
buffered: currentChapterBuffered ?? totalBuffered,
buffered:
currentChapterBuffered ?? buffered.data ?? const Duration(seconds: 0),
bufferedBarColor: Theme.of(context).colorScheme.secondary,
timeLabelType: TimeLabelType.remainingTime,
timeLabelLocation: TimeLabelLocation.below,