完善新播放逻辑

This commit is contained in:
rang 2025-11-22 15:54:29 +08:00
parent eb1955e5e6
commit 114c9761fd
30 changed files with 658 additions and 683 deletions

View file

@ -1,103 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart';
import 'package:vaani/features/player/providers/audiobook_player.dart';
import 'package:vaani/features/player/view/player_expanded.dart';
import 'package:vaani/settings/view/notification_settings_page.dart';
class SkipChapterStartEndButton extends HookConsumerWidget {
const SkipChapterStartEndButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Tooltip(
message: "跳过片头片尾",
child: IconButton(
icon: const Icon(Icons.fast_forward_rounded),
onPressed: () async {
// show toast
pendingPlayerModals++;
await showModalBottomSheet<bool>(
context: context,
barrierLabel: '跳过片头片尾',
constraints: BoxConstraints(
// 40% of the screen height
maxHeight: MediaQuery.of(context).size.height * 0.4,
),
builder: (context) {
return const Padding(
padding: EdgeInsets.all(8.0),
child: PlayerSkipChapterStartEnd(),
);
},
);
pendingPlayerModals--;
},
),
);
}
}
class PlayerSkipChapterStartEnd extends HookConsumerWidget {
const PlayerSkipChapterStartEnd({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final player = ref.watch(audiobookPlayerProvider);
final bookId = player.book?.libraryItemId ?? '_';
final bookSettings = ref.watch(bookSettingsProvider(bookId));
return Scaffold(
body: Column(
children: [
ListTile(
title: Text(
'跳过片头 ${bookSettings.playerSettings.skipChapterStart.inSeconds}s'),
),
Expanded(
child: TimeIntervalSlider(
defaultValue: bookSettings.playerSettings.skipChapterStart,
// defaultValue: const Duration(seconds: 0),
min: const Duration(seconds: 0),
max: const Duration(seconds: 60),
step: const Duration(seconds: 1),
onChangedEnd: (interval) {
ref
.read(
bookSettingsProvider(bookId).notifier,
)
.update(
bookSettings.copyWith
.playerSettings(skipChapterStart: interval),
);
ref.read(audiobookPlayerProvider).setClip(start: interval);
},
),
),
ListTile(
title: Text(
'跳过片尾 ${bookSettings.playerSettings.skipChapterEnd.inSeconds}s'),
),
Expanded(
child: TimeIntervalSlider(
defaultValue: bookSettings.playerSettings.skipChapterEnd,
// defaultValue: const Duration(seconds: 0),
min: const Duration(seconds: 0),
max: const Duration(seconds: 60),
step: const Duration(seconds: 1),
onChangedEnd: (interval) {
ref
.read(
bookSettingsProvider(bookId).notifier,
)
.update(
bookSettings.copyWith
.playerSettings(skipChapterEnd: interval),
);
},
),
),
],
),
);
}
}

View file

@ -1,112 +1,48 @@
import 'dart:async';
import 'package:shelfsdk/audiobookshelf_api.dart';
import 'package:vaani/features/player/core/audiobook_player.dart';
import 'package:vaani/features/player/core/audiobook_player_session.dart';
import 'package:vaani/shared/extensions/chapter.dart';
import 'package:vaani/shared/utils/throttler.dart';
class SkipStartEnd {
final Duration start;
final Duration end;
final AudiobookPlayer player;
// id
int? chapterId;
// int _index;
final AbsAudioHandler player;
final List<StreamSubscription> _subscriptions = [];
final throttler = Throttler(delay: Duration(seconds: 3));
// final StreamController<PlaybackEvent> _playbackController =
// StreamController<PlaybackEvent>.broadcast();
final throttlerStart = Throttler(delay: Duration(seconds: 3));
final throttlerEnd = Throttler(delay: Duration(seconds: 3));
SkipStartEnd({
required this.start,
required this.end,
required this.player,
this.chapterId,
}) {
// if (start > Duration()) {
// _subscriptions.add(
// player.currentIndexStream.listen((index) {
// if (_index != index && player.position.inMilliseconds < 500) {
// Future.microtask(() {
// player.seek(start);
// });
// _index = index!;
// }
// }),
// );
// }
// if (end > Duration()) {
// _subscriptions.add(
// player.positionStream.distinct().listen((position) {
// if (player.duration != null &&
// player.duration!.inMilliseconds - player.position.inMilliseconds <
// end.inMilliseconds) {
// throttler.call(() {
// print('跳过片尾');
// Future.microtask(() async {
// await player.stop();
// player.seekToNext();
// });
// });
// }
// }),
// );
// }
if (start > Duration.zero || end > Duration.zero) {
if (start > Duration.zero) {
_subscriptions.add(
player.positionStream.listen((position) {
final chapter = player.currentChapter;
if (chapter == null) {
return;
}
if (chapter.id == chapterId) {
if (end > Duration.zero &&
chapter.duration - (player.positionInBook - chapter.start) <
end) {
throttler.call(() {
Future.microtask(() => skipEnd(chapter));
});
}
}
if (chapter.id != chapterId) {
if (start > Duration.zero &&
player.positionInBook - chapter.start < Duration(seconds: 1)) {
throttler.call(() {
Future.microtask(() => skipStart(chapter));
});
}
chapterId = chapter.id;
player.chapterStream.listen((chapter) {
if (chapter != null &&
player.positionInChapter < Duration(seconds: 1)) {
Future.microtask(
() => throttlerStart
.call(() => player.seekInBook(chapter.start + start)),
);
}
}),
);
}
}
void skipStart(BookChapter chapter) {
print('跳过片头');
final globalPosition = player.positionInBook;
if (globalPosition - chapter.start < Duration(seconds: 1)) {
player.seekInBook(chapter.start + start);
}
}
void skipEnd(chapter) {
print('跳过片尾');
final book = player.book;
if (book == null) {
return;
}
if (start > Duration.zero) {
final currentIndex = book.chapters.indexOf(chapter);
if (currentIndex < book.chapters.length - 1) {
final nextChapter = book.chapters[currentIndex + 1];
// +
print('跳过片头+片尾');
player.skipToChapter(nextChapter.id, position: start);
}
} else {
player.seekToPrevious();
if (end > Duration.zero) {
_subscriptions.add(
player.positionStreamInChapter.listen((positionChapter) {
if (end >
(player.currentChapter?.duration ?? Duration.zero) -
positionChapter) {
Future.microtask(
() => throttlerEnd.call(() => player.skipToNext()),
);
}
}),
);
}
}
@ -115,7 +51,8 @@ class SkipStartEnd {
for (var sub in _subscriptions) {
sub.cancel();
}
throttler.dispose();
throttlerStart.dispose();
throttlerEnd.dispose();
// _playbackController.close();
}
}

View file

@ -1,6 +1,6 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:vaani/features/per_book_settings/providers/book_settings_provider.dart';
import 'package:vaani/features/player/providers/audiobook_player.dart';
import 'package:vaani/features/player/providers/session_provider.dart';
import 'package:vaani/features/skip_start_end/skip_start_end.dart' as core;
part 'skip_start_end_provider.g.dart';
@ -9,23 +9,51 @@ part 'skip_start_end_provider.g.dart';
class SkipStartEnd extends _$SkipStartEnd {
@override
core.SkipStartEnd? build() {
final player = ref.watch(simpleAudiobookPlayerProvider);
final book = ref.watch(audiobookPlayerProvider.select((v) => v.book));
final bookId = book?.libraryItemId ?? '_';
if (bookId == '_') {
final session = ref.watch(sessionProvider);
final bookId = session?.libraryItemId;
if (session == null || bookId == null) {
return null;
}
final player = ref.read(playerProvider);
final bookSettings = ref.watch(bookSettingsProvider(bookId));
final start = bookSettings.playerSettings.skipChapterStart;
final end = bookSettings.playerSettings.skipChapterEnd;
if (start < Duration.zero && end < Duration.zero) {
return null;
}
final skipStartEnd = core.SkipStartEnd(
start: start,
end: end,
player: player,
chapterId: player.currentChapter?.id,
);
ref.onDispose(skipStartEnd.dispose);
return skipStartEnd;
}
}
// @riverpod
// class SkipStartEnd extends _$SkipStartEnd {
// @override
// core.SkipStartEnd? build() {
// final player = ref.watch(simpleAudiobookPlayerProvider);
// final book = ref.watch(audiobookPlayerProvider.select((v) => v.book));
// final bookId = book?.libraryItemId ?? '_';
// if (bookId == '_') {
// return null;
// }
// final bookSettings = ref.watch(bookSettingsProvider(bookId));
// final start = bookSettings.playerSettings.skipChapterStart;
// final end = bookSettings.playerSettings.skipChapterEnd;
// final skipStartEnd = core.SkipStartEnd(
// start: start,
// end: end,
// player: player,
// chapterId: player.currentChapter?.id,
// );
// ref.onDispose(skipStartEnd.dispose);
// return skipStartEnd;
// }
// }

View file

@ -6,7 +6,7 @@ part of 'skip_start_end_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$skipStartEndHash() => r'857b448eac9bb9ab85cea9217775712e660bc990';
String _$skipStartEndHash() => r'6df119db598c6e8673dcea090ad97f5affab4016';
/// See also [SkipStartEnd].
@ProviderFor(SkipStartEnd)