mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2026-02-16 06:19:35 +00:00
123
This commit is contained in:
parent
634ffaed8c
commit
edd5a01482
9 changed files with 997 additions and 950 deletions
|
|
@ -174,8 +174,7 @@ class AbsAudioPlayer {
|
|||
}
|
||||
|
||||
Future<void> seek(Duration position, {int? index}) async {
|
||||
await _player.seek(_addClippingStart(_player.position, add: false),
|
||||
index: index);
|
||||
await _player.seek(_addClippingStart(position, add: false), index: index);
|
||||
}
|
||||
|
||||
Future<void> setSpeed(double speed) async {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||
import 'package:vaani/constants/sizes.dart';
|
||||
import 'package:vaani/features/item_viewer/view/library_item_hero_section.dart';
|
||||
import 'package:vaani/features/player/providers/abs_provider.dart';
|
||||
import 'package:vaani/features/player/view/player_expanded.dart'
|
||||
show PlayerExpandedImage;
|
||||
import 'package:vaani/features/player/view/player_minimized.dart';
|
||||
import 'package:vaani/features/player/view/widgets/audiobook_player_seek_button.dart';
|
||||
import 'package:vaani/features/player/view/widgets/audiobook_player_seek_chapter_button.dart';
|
||||
import 'package:vaani/features/player/view/widgets/chapter_selection_button.dart';
|
||||
|
|
@ -16,6 +18,7 @@ import 'package:vaani/features/player/view/widgets/player_speed_adjust_button.da
|
|||
import 'package:vaani/features/skip_start_end/view/skip_start_end_button.dart';
|
||||
import 'package:vaani/features/sleep_timer/view/sleep_timer_button.dart';
|
||||
import 'package:vaani/globals.dart';
|
||||
import 'package:vaani/shared/extensions/model_conversions.dart';
|
||||
|
||||
var pendingPlayerModals = 0;
|
||||
|
||||
|
|
@ -40,7 +43,7 @@ class PlayerExpandedDesktop extends HookConsumerWidget {
|
|||
final availWidth = MediaQuery.of(context).size.width;
|
||||
// the image width when the player is expanded
|
||||
final imageSize = min(playerMaxHeight * 0.5, availWidth * 0.9);
|
||||
|
||||
final itemBookMetadata = book.metadata.asBookMetadataExpanded;
|
||||
return Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
|
|
@ -62,18 +65,7 @@ class PlayerExpandedDesktop extends HookConsumerWidget {
|
|||
// add a shadow to the image elevation hovering effect
|
||||
child: PlayerExpandedImage(imageSize),
|
||||
),
|
||||
// _buildControls(imageSize),
|
||||
// SizedBox(
|
||||
// width: imageSize,
|
||||
// child: Padding(
|
||||
// padding: EdgeInsets.only(
|
||||
// left: AppElementSizes.paddingRegular,
|
||||
// right: AppElementSizes.paddingRegular,
|
||||
// ),
|
||||
// child: const AudiobookChapterProgressBar(),
|
||||
// ),
|
||||
// ),
|
||||
_buildSettings(imageSize),
|
||||
_buildBookDetails(imageSize, itemBookMetadata),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -113,9 +105,19 @@ class PlayerExpandedDesktop extends HookConsumerWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: playerMinimizedHeight,
|
||||
child: _buildBottom(),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: AppElementSizes.paddingRegular,
|
||||
),
|
||||
child: const AudiobookChapterProgressBar(
|
||||
timeLabelLocation: TimeLabelLocation.sides,
|
||||
),
|
||||
),
|
||||
Container(child: _buildBottom()),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
|
@ -123,67 +125,57 @@ class PlayerExpandedDesktop extends HookConsumerWidget {
|
|||
|
||||
Widget _buildBottom() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 180,
|
||||
Spacer(),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
const AudiobookPlayerSeekChapterButton(isForward: false),
|
||||
const AudiobookPlayerSeekButton(isForward: false),
|
||||
// play/pause button
|
||||
const AudiobookPlayerPlayPauseButton(),
|
||||
const AudiobookPlayerSeekButton(isForward: true),
|
||||
const AudiobookPlayerSeekChapterButton(isForward: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: AppElementSizes.paddingRegular,
|
||||
right: AppElementSizes.paddingRegular,
|
||||
),
|
||||
child: const AudiobookChapterProgressBar(),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
const PlayerSpeedAdjustButton(),
|
||||
const SleepTimerButton(),
|
||||
SkipChapterStartEndButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildControls(double width) {
|
||||
return SizedBox(
|
||||
Widget _buildBookDetails(
|
||||
double width, BookMetadataExpanded? itemBookMetadata) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(AppElementSizes.paddingLarge),
|
||||
width: width,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
child: Column(
|
||||
children: [
|
||||
// previous chapter
|
||||
const AudiobookPlayerSeekChapterButton(isForward: false),
|
||||
// buttonSkipBackwards
|
||||
const AudiobookPlayerSeekButton(isForward: false),
|
||||
const AudiobookPlayerPlayPauseButton(),
|
||||
// // buttonSkipForwards
|
||||
const AudiobookPlayerSeekButton(isForward: true),
|
||||
// // next chapter
|
||||
const AudiobookPlayerSeekChapterButton(isForward: true),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSettings(double width) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
const AudiobookPlayerSeekButton(isForward: false),
|
||||
const AudiobookPlayerSeekButton(isForward: true),
|
||||
// speed control
|
||||
const PlayerSpeedAdjustButton(),
|
||||
const Spacer(),
|
||||
// sleep timer
|
||||
const SleepTimerButton(),
|
||||
const Spacer(),
|
||||
// 跳过片头片尾
|
||||
SkipChapterStartEndButton(),
|
||||
// authors info if available
|
||||
BookAuthors(
|
||||
itemBookMetadata: itemBookMetadata,
|
||||
bookDetailsCached: null,
|
||||
),
|
||||
// narrators info if available
|
||||
BookNarrators(
|
||||
itemBookMetadata: itemBookMetadata,
|
||||
bookDetailsCached: null,
|
||||
),
|
||||
// series info if available
|
||||
BookSeries(
|
||||
itemBookMetadata: itemBookMetadata,
|
||||
bookDetailsCached: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -6,14 +6,12 @@ import 'package:vaani/constants/sizes.dart';
|
|||
import 'package:vaani/features/player/providers/abs_provider.dart';
|
||||
import 'package:vaani/features/player/view/widgets/audiobook_player_seek_chapter_button.dart';
|
||||
import 'package:vaani/features/player/view/widgets/player_player_pause_button.dart';
|
||||
import 'package:vaani/globals.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';
|
||||
|
||||
/// The height of the player when it is minimized
|
||||
const double playerMinimizedHeight = 70;
|
||||
|
||||
class PlayerMinimized extends HookConsumerWidget {
|
||||
const PlayerMinimized({super.key});
|
||||
|
||||
|
|
@ -26,19 +24,17 @@ class PlayerMinimized extends HookConsumerWidget {
|
|||
final size = MediaQuery.of(context).size;
|
||||
// 竖屏
|
||||
final isVertical = size.height > size.width;
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
height: playerMinimizedHeight,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
isVertical
|
||||
? const PlayerMinimizedControls()
|
||||
: const PlayerMinimizedControlsDesktop(),
|
||||
const PlayerMinimizedProgress(),
|
||||
],
|
||||
),
|
||||
return Container(
|
||||
height: playerMinHeight,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
isVertical
|
||||
? const PlayerMinimizedControls()
|
||||
: const PlayerMinimizedControlsDesktop(),
|
||||
const PlayerMinimizedProgress(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -81,7 +77,7 @@ class PlayerMinimizedControls extends HookConsumerWidget {
|
|||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: playerMinimizedHeight,
|
||||
maxWidth: playerMinHeight,
|
||||
),
|
||||
child: BookCoverWidget(),
|
||||
),
|
||||
|
|
@ -174,89 +170,91 @@ class PlayerMinimizedControlsDesktop extends HookConsumerWidget {
|
|||
final currentBook = ref.watch(currentBookProvider);
|
||||
final currentChapter = ref.watch(currentChapterProvider);
|
||||
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (GoRouterState.of(context).topRoute?.name != Routes.player.name) {
|
||||
context.pushNamed(Routes.player.name);
|
||||
} else {
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 180,
|
||||
child: Row(
|
||||
children: [
|
||||
const AudiobookPlayerSeekChapterButton(isForward: false),
|
||||
// play/pause button
|
||||
const AudiobookPlayerPlayPauseButton(),
|
||||
const AudiobookPlayerSeekChapterButton(isForward: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// image
|
||||
Padding(
|
||||
padding: EdgeInsets.all(AppElementSizes.paddingSmall),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// navigate to item page
|
||||
if (currentBook != null) {
|
||||
context.pushNamed(
|
||||
Routes.libraryItem.name,
|
||||
pathParameters: {
|
||||
Routes.libraryItem.pathParamName!:
|
||||
currentBook.libraryItemId,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: playerMinimizedHeight,
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.click, // 桌面端显示手型光标
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (GoRouterState.of(context).topRoute?.name != Routes.player.name) {
|
||||
context.pushNamed(Routes.player.name);
|
||||
} else {
|
||||
context.pop();
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
// image
|
||||
Padding(
|
||||
padding: EdgeInsets.all(AppElementSizes.paddingSmall),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// navigate to item page
|
||||
if (currentBook != null) {
|
||||
context.pushNamed(
|
||||
Routes.libraryItem.name,
|
||||
pathParameters: {
|
||||
Routes.libraryItem.pathParamName!:
|
||||
currentBook.libraryItemId,
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: playerMinHeight,
|
||||
),
|
||||
child: BookCoverWidget(),
|
||||
),
|
||||
child: BookCoverWidget(),
|
||||
),
|
||||
),
|
||||
),
|
||||
// author and title of the book
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: AppElementSizes.paddingRegular,
|
||||
// author and title of the book
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: AppElementSizes.paddingRegular,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// AutoScrollText(
|
||||
Text(
|
||||
'${currentBook?.metadata.title ?? ''} - ${currentChapter?.title ?? ''}',
|
||||
maxLines: 1, overflow: TextOverflow.ellipsis,
|
||||
// velocity:
|
||||
// const Velocity(pixelsPerSecond: Offset(16, 0)),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
currentBook?.metadata.asBookMetadataExpanded.authorName ??
|
||||
'',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
),
|
||||
SizedBox(
|
||||
width: 180,
|
||||
child: Row(
|
||||
children: [
|
||||
// AutoScrollText(
|
||||
Text(
|
||||
'${currentBook?.metadata.title ?? ''} - ${currentChapter?.title ?? ''}',
|
||||
maxLines: 1, overflow: TextOverflow.ellipsis,
|
||||
// velocity:
|
||||
// const Velocity(pixelsPerSecond: Offset(16, 0)),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
currentBook?.metadata.asBookMetadataExpanded.authorName ??
|
||||
'',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface
|
||||
.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
const AudiobookPlayerSeekChapterButton(isForward: false),
|
||||
// play/pause button
|
||||
const AudiobookPlayerPlayPauseButton(),
|
||||
const AudiobookPlayerSeekChapterButton(isForward: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||
import 'package:vaani/constants/sizes.dart';
|
||||
import 'package:vaani/features/player/providers/abs_provider.dart';
|
||||
|
||||
// 章节进度
|
||||
class AudiobookChapterProgressBar extends HookConsumerWidget {
|
||||
final TimeLabelLocation timeLabelLocation;
|
||||
const AudiobookChapterProgressBar({
|
||||
super.key,
|
||||
this.timeLabelLocation = TimeLabelLocation.below,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -54,11 +57,12 @@ class AudiobookChapterProgressBar extends HookConsumerWidget {
|
|||
currentChapterBuffered ?? buffered.data ?? const Duration(seconds: 0),
|
||||
bufferedBarColor: Theme.of(context).colorScheme.secondary,
|
||||
timeLabelType: TimeLabelType.remainingTime,
|
||||
timeLabelLocation: TimeLabelLocation.below,
|
||||
timeLabelLocation: timeLabelLocation,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 书籍进度 简化版
|
||||
class AudiobookProgressBar extends HookConsumerWidget {
|
||||
const AudiobookProgressBar({
|
||||
super.key,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue