mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-02-16 14:29:35 +00:00
增加跳过片头片尾,上一章下一章移动到AudioPlayer对象中
This commit is contained in:
parent
e06c834d0e
commit
620a1eb7a2
29 changed files with 1080 additions and 179 deletions
|
|
@ -51,7 +51,18 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
AudiobookPlayer(this.token, this.baseUrl) : super() {
|
||||
// set the source of the player to the first track in the book
|
||||
_logger.config('Setting up audiobook player');
|
||||
// currentIndexStream.listen((index) {
|
||||
// print('播放器已切换到第 $index 首曲目');
|
||||
// skip = true;
|
||||
// });
|
||||
// positionStream.listen((position) {
|
||||
// if (skip != null && skip! && position.inSeconds < 20) {
|
||||
// super.seek(Duration(seconds: 20));
|
||||
// print('播放 $position');
|
||||
// }
|
||||
// });
|
||||
}
|
||||
bool? skip;
|
||||
|
||||
/// the [BookExpanded] being played
|
||||
BookExpanded? _book;
|
||||
|
|
@ -114,9 +125,8 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
// initialPosition ;
|
||||
final trackToPlay = getTrackToPlay(book, initialPosition ?? Duration.zero);
|
||||
final initialIndex = book.tracks.indexOf(trackToPlay);
|
||||
final initialPositionInTrack = initialPosition != null
|
||||
? initialPosition - trackToPlay.startOffset
|
||||
: null;
|
||||
final initialPositionInTrack =
|
||||
initialPosition != null ? initialPosition - trackToPlay.startOffset : null;
|
||||
|
||||
_logger.finer('Setting audioSource');
|
||||
await setAudioSource(
|
||||
|
|
@ -126,8 +136,7 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
ConcatenatingAudioSource(
|
||||
useLazyPreparation: true,
|
||||
children: book.tracks.map((track) {
|
||||
final retrievedUri =
|
||||
_getUri(track, downloadedUris, baseUrl: baseUrl, token: token);
|
||||
final retrievedUri = _getUri(track, downloadedUris, baseUrl: baseUrl, token: token);
|
||||
_logger.fine(
|
||||
'Setting source for track: ${track.title}, URI: ${retrievedUri.obfuscate()}',
|
||||
);
|
||||
|
|
@ -137,10 +146,8 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
// Specify a unique ID for each media item:
|
||||
id: book.libraryItemId + track.index.toString(),
|
||||
// Metadata to display in the notification:
|
||||
title: appSettings.notificationSettings.primaryTitle
|
||||
.formatNotificationTitle(book),
|
||||
album: appSettings.notificationSettings.secondaryTitle
|
||||
.formatNotificationTitle(book),
|
||||
title: appSettings.notificationSettings.primaryTitle.formatNotificationTitle(book),
|
||||
album: appSettings.notificationSettings.secondaryTitle.formatNotificationTitle(book),
|
||||
artUri: artworkUri ??
|
||||
Uri.parse(
|
||||
'$baseUrl/api/items/${book.libraryItemId}/cover?token=$token&width=800',
|
||||
|
|
@ -172,7 +179,10 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
/// so we need to calculate the duration and current position based on the book
|
||||
|
||||
@override
|
||||
Future<void> seek(Duration? positionInBook, {int? index}) async {
|
||||
Future<void> seek(Duration? positionInBook, {int? index, bool b = true}) async {
|
||||
if (!b) {
|
||||
return super.seek(positionInBook, index: index);
|
||||
}
|
||||
if (_book == null) {
|
||||
_logger.warning('No book is set, not seeking');
|
||||
return;
|
||||
|
|
@ -183,9 +193,43 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
}
|
||||
final tracks = _book!.tracks;
|
||||
final trackToPlay = getTrackToPlay(_book!, positionInBook);
|
||||
final index = tracks.indexOf(trackToPlay);
|
||||
final i = tracks.indexOf(trackToPlay);
|
||||
final positionInTrack = positionInBook - trackToPlay.startOffset;
|
||||
return super.seek(positionInTrack, index: index);
|
||||
return super.seek(positionInTrack, index: i);
|
||||
}
|
||||
|
||||
// add a small offset so the display does not show the previous chapter for a split second
|
||||
final offset = Duration(milliseconds: 10);
|
||||
|
||||
/// time into the current chapter to determine if we should go to the previous chapter or the start of the current chapter
|
||||
final doNotSeekBackIfLessThan = Duration(seconds: 5);
|
||||
|
||||
/// seek forward to the next chapter
|
||||
void seekForward() {
|
||||
final index = _book!.chapters.indexOf(currentChapter!);
|
||||
if (index < _book!.chapters.length - 1) {
|
||||
seek(
|
||||
_book!.chapters[index + 1].start + offset,
|
||||
);
|
||||
} else {
|
||||
seek(currentChapter!.end);
|
||||
}
|
||||
}
|
||||
|
||||
/// seek backward to the previous chapter or the start of the current chapter
|
||||
void seekBackward() {
|
||||
final currentPlayingChapterIndex = _book!.chapters.indexOf(currentChapter!);
|
||||
final chapterPosition = positionInBook - currentChapter!.start;
|
||||
BookChapter chapterToSeekTo;
|
||||
// if player position is less than 5 seconds into the chapter, go to the previous chapter
|
||||
if (chapterPosition < doNotSeekBackIfLessThan && currentPlayingChapterIndex > 0) {
|
||||
chapterToSeekTo = _book!.chapters[currentPlayingChapterIndex - 1];
|
||||
} else {
|
||||
chapterToSeekTo = currentChapter!;
|
||||
}
|
||||
seek(
|
||||
chapterToSeekTo.start + offset,
|
||||
);
|
||||
}
|
||||
|
||||
/// a convenience method to get position in the book instead of the current track position
|
||||
|
|
@ -201,8 +245,7 @@ class AudiobookPlayer extends AudioPlayer {
|
|||
if (_book == null) {
|
||||
return Duration.zero;
|
||||
}
|
||||
return bufferedPosition +
|
||||
_book!.tracks[sequenceState!.currentIndex].startOffset;
|
||||
return bufferedPosition + _book!.tracks[sequenceState!.currentIndex].startOffset;
|
||||
}
|
||||
|
||||
/// streams to override to suit the book instead of the current track
|
||||
|
|
@ -277,8 +320,7 @@ Uri _getUri(
|
|||
},
|
||||
);
|
||||
|
||||
return uri ??
|
||||
Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token');
|
||||
return uri ?? Uri.parse('${baseUrl.toString()}${track.contentUrl}?token=$token');
|
||||
}
|
||||
|
||||
extension FormatNotificationTitle on String {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:vaani/constants/sizes.dart';
|
|||
import 'package:vaani/features/player/providers/currently_playing_provider.dart';
|
||||
import 'package:vaani/features/player/providers/player_form.dart';
|
||||
import 'package:vaani/features/player/view/audiobook_player.dart';
|
||||
import 'package:vaani/features/skip_start_end/player_skip_chapter_start_end.dart';
|
||||
import 'package:vaani/features/sleep_timer/view/sleep_timer_button.dart';
|
||||
import 'package:vaani/shared/extensions/inverse_lerp.dart';
|
||||
import 'package:vaani/shared/widgets/not_implemented.dart';
|
||||
|
|
@ -245,6 +246,9 @@ class PlayerWhenExpanded extends HookConsumerWidget {
|
|||
const Spacer(),
|
||||
// chapter list
|
||||
const ChapterSelectionButton(),
|
||||
const Spacer(),
|
||||
// 跳过片头片尾
|
||||
SkipChapterStartEndButton(),
|
||||
// settings
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.more_horiz),
|
||||
|
|
|
|||
|
|
@ -17,42 +17,42 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final player = ref.watch(audiobookPlayerProvider);
|
||||
|
||||
// add a small offset so the display does not show the previous chapter for a split second
|
||||
const offset = Duration(milliseconds: 10);
|
||||
// // add a small offset so the display does not show the previous chapter for a split second
|
||||
// const offset = Duration(milliseconds: 10);
|
||||
|
||||
/// time into the current chapter to determine if we should go to the previous chapter or the start of the current chapter
|
||||
const doNotSeekBackIfLessThan = Duration(seconds: 5);
|
||||
// /// time into the current chapter to determine if we should go to the previous chapter or the start of the current chapter
|
||||
// const doNotSeekBackIfLessThan = Duration(seconds: 5);
|
||||
|
||||
/// seek forward to the next chapter
|
||||
void seekForward() {
|
||||
final index = player.book!.chapters.indexOf(player.currentChapter!);
|
||||
if (index < player.book!.chapters.length - 1) {
|
||||
player.seek(
|
||||
player.book!.chapters[index + 1].start + offset,
|
||||
);
|
||||
} else {
|
||||
player.seek(player.currentChapter!.end);
|
||||
}
|
||||
}
|
||||
// /// seek forward to the next chapter
|
||||
// void seekForward() {
|
||||
// final index = player.book!.chapters.indexOf(player.currentChapter!);
|
||||
// if (index < player.book!.chapters.length - 1) {
|
||||
// player.seek(
|
||||
// player.book!.chapters[index + 1].start + offset,
|
||||
// );
|
||||
// } else {
|
||||
// player.seek(player.currentChapter!.end);
|
||||
// }
|
||||
// }
|
||||
|
||||
/// seek backward to the previous chapter or the start of the current chapter
|
||||
void seekBackward() {
|
||||
final currentPlayingChapterIndex =
|
||||
player.book!.chapters.indexOf(player.currentChapter!);
|
||||
final chapterPosition =
|
||||
player.positionInBook - player.currentChapter!.start;
|
||||
BookChapter chapterToSeekTo;
|
||||
// if player position is less than 5 seconds into the chapter, go to the previous chapter
|
||||
if (chapterPosition < doNotSeekBackIfLessThan &&
|
||||
currentPlayingChapterIndex > 0) {
|
||||
chapterToSeekTo = player.book!.chapters[currentPlayingChapterIndex - 1];
|
||||
} else {
|
||||
chapterToSeekTo = player.currentChapter!;
|
||||
}
|
||||
player.seek(
|
||||
chapterToSeekTo.start + offset,
|
||||
);
|
||||
}
|
||||
// /// seek backward to the previous chapter or the start of the current chapter
|
||||
// void seekBackward() {
|
||||
// final currentPlayingChapterIndex =
|
||||
// player.book!.chapters.indexOf(player.currentChapter!);
|
||||
// final chapterPosition =
|
||||
// player.positionInBook - player.currentChapter!.start;
|
||||
// BookChapter chapterToSeekTo;
|
||||
// // if player position is less than 5 seconds into the chapter, go to the previous chapter
|
||||
// if (chapterPosition < doNotSeekBackIfLessThan &&
|
||||
// currentPlayingChapterIndex > 0) {
|
||||
// chapterToSeekTo = player.book!.chapters[currentPlayingChapterIndex - 1];
|
||||
// } else {
|
||||
// chapterToSeekTo = player.currentChapter!;
|
||||
// }
|
||||
// player.seek(
|
||||
// chapterToSeekTo.start + offset,
|
||||
// );
|
||||
// }
|
||||
|
||||
return IconButton(
|
||||
icon: Icon(
|
||||
|
|
@ -69,9 +69,9 @@ class AudiobookPlayerSeekChapterButton extends HookConsumerWidget {
|
|||
return;
|
||||
}
|
||||
if (isForward) {
|
||||
seekForward();
|
||||
player.seekForward();
|
||||
} else {
|
||||
seekBackward();
|
||||
player.seekBackward();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:vaani/features/player/providers/audiobook_player.dart'
|
||||
show audiobookPlayerProvider;
|
||||
import 'package:vaani/features/player/providers/audiobook_player.dart' show audiobookPlayerProvider;
|
||||
import 'package:vaani/features/player/providers/currently_playing_provider.dart'
|
||||
show currentPlayingChapterProvider, currentlyPlayingBookProvider;
|
||||
import 'package:vaani/features/player/view/player_when_expanded.dart'
|
||||
show pendingPlayerModals;
|
||||
import 'package:vaani/features/player/view/player_when_expanded.dart' show pendingPlayerModals;
|
||||
import 'package:vaani/features/player/view/widgets/playing_indicator_icon.dart';
|
||||
import 'package:vaani/main.dart' show appLogger;
|
||||
import 'package:vaani/shared/extensions/chapter.dart' show ChapterDuration;
|
||||
import 'package:vaani/shared/extensions/duration_format.dart'
|
||||
show DurationFormat;
|
||||
import 'package:vaani/shared/extensions/duration_format.dart' show DurationFormat;
|
||||
import 'package:vaani/shared/hooks.dart' show useTimer;
|
||||
|
||||
class ChapterSelectionButton extends HookConsumerWidget {
|
||||
|
|
@ -70,7 +67,7 @@ class ChapterSelectionModal extends HookConsumerWidget {
|
|||
);
|
||||
}
|
||||
|
||||
useTimer(scrollToCurrentChapter, 500.ms);
|
||||
useTimer(scrollToCurrentChapter, 100.ms);
|
||||
// useInterval(scrollToCurrentChapter, 500.ms);
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
|
|
@ -84,19 +81,18 @@ class ChapterSelectionModal extends HookConsumerWidget {
|
|||
Expanded(
|
||||
child: Scrollbar(
|
||||
child: SingleChildScrollView(
|
||||
primary: true,
|
||||
child: currentBook?.chapters == null
|
||||
? const Text('No chapters found')
|
||||
: Column(
|
||||
children: currentBook!.chapters.map(
|
||||
(chapter) {
|
||||
final isCurrent = currentChapterIndex == chapter.id;
|
||||
final isPlayed = currentChapterIndex != null &&
|
||||
chapter.id < currentChapterIndex;
|
||||
final isPlayed =
|
||||
currentChapterIndex != null && chapter.id < currentChapterIndex;
|
||||
return ListTile(
|
||||
autofocus: isCurrent,
|
||||
iconColor: isPlayed && !isCurrent
|
||||
? theme.disabledColor
|
||||
: null,
|
||||
iconColor: isPlayed && !isCurrent ? theme.disabledColor : null,
|
||||
title: Text(
|
||||
chapter.title,
|
||||
style: isPlayed && !isCurrent
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue