mirror of
https://github.com/ente-io/ente.git
synced 2025-08-07 23:18:10 +00:00
fix: seek bar & buffering
This commit is contained in:
parent
94a77b7df1
commit
82fdae9253
@ -244,6 +244,7 @@ class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
|
||||
}
|
||||
});
|
||||
_chewieController = ChewieController(
|
||||
progressIndicatorDelay: const Duration(milliseconds: 200),
|
||||
videoPlayerController: _videoPlayerController!,
|
||||
aspectRatio: _videoPlayerController!.value.aspectRatio,
|
||||
autoPlay: widget.autoPlay!,
|
||||
|
@ -1,13 +1,18 @@
|
||||
// ignore_for_file: implementation_imports
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:chewie/chewie.dart';
|
||||
import "package:chewie/chewie.dart";
|
||||
import "package:chewie/src/helpers/utils.dart";
|
||||
import "package:chewie/src/notifiers/index.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:photos/models/file/file.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/common/loading_widget.dart";
|
||||
import "package:photos/ui/viewer/file/preview_status_widget.dart";
|
||||
import "package:photos/utils/debouncer.dart";
|
||||
import "package:photos/ui/viewer/file/video_control/custom_progress_bar.dart";
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
|
||||
class VideoControls extends StatefulWidget {
|
||||
@ -27,9 +32,10 @@ class VideoControls extends StatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _VideoControlsState extends State<VideoControls> {
|
||||
VideoPlayerValue? _latestValue;
|
||||
bool _hideStuff = true;
|
||||
class _VideoControlsState extends State<VideoControls>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late PlayerNotifier notifier;
|
||||
late VideoPlayerValue _latestValue;
|
||||
Timer? _hideTimer;
|
||||
Timer? _initTimer;
|
||||
Timer? _showAfterExpandCollapseTimer;
|
||||
@ -38,34 +44,35 @@ class _VideoControlsState extends State<VideoControls> {
|
||||
Timer? _bufferingDisplayTimer;
|
||||
bool _displayBufferingIndicator = false;
|
||||
|
||||
final barHeight = 120.0;
|
||||
final barHeight = 48.0 * 1.5;
|
||||
final marginSize = 5.0;
|
||||
|
||||
late VideoPlayerController controller;
|
||||
ChewieController? chewieController;
|
||||
ChewieController? _chewieController;
|
||||
|
||||
void _bufferingTimerTimeout() {
|
||||
_displayBufferingIndicator = true;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
// We know that _chewieController is set in didChangeDependencies
|
||||
ChewieController get chewieController => _chewieController!;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
notifier = Provider.of<PlayerNotifier>(context, listen: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_latestValue!.hasError) {
|
||||
return chewieController!.errorBuilder != null
|
||||
? chewieController!.errorBuilder!(
|
||||
context,
|
||||
chewieController!.videoPlayerController.value.errorDescription!,
|
||||
)
|
||||
: Center(
|
||||
child: Icon(
|
||||
Icons.error,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
size: 42,
|
||||
),
|
||||
);
|
||||
if (_latestValue.hasError) {
|
||||
return chewieController.errorBuilder?.call(
|
||||
context,
|
||||
chewieController.videoPlayerController.value.errorDescription!,
|
||||
) ??
|
||||
Center(
|
||||
child: Icon(
|
||||
Icons.error,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
size: 42,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
@ -75,43 +82,35 @@ class _VideoControlsState extends State<VideoControls> {
|
||||
child: GestureDetector(
|
||||
onTap: () => _cancelAndRestartTimer(),
|
||||
child: AbsorbPointer(
|
||||
absorbing: _hideStuff,
|
||||
absorbing: notifier.hideStuff,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
if (_latestValue != null &&
|
||||
!_latestValue!.isPlaying &&
|
||||
_latestValue!.isBuffering ||
|
||||
_displayBufferingIndicator)
|
||||
const Align(
|
||||
alignment: Alignment.center,
|
||||
child: Center(
|
||||
child: EnteLoadingWidget(
|
||||
size: 32,
|
||||
color: fillBaseDark,
|
||||
padding: 0,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Positioned.fill(child: _buildHitArea()),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
left: false,
|
||||
right: false,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
PreviewStatusWidget(
|
||||
showControls: !_hideStuff,
|
||||
file: widget.file,
|
||||
isPreviewPlayer: true,
|
||||
onStreamChange: widget.onStreamChange,
|
||||
children: [
|
||||
if (_displayBufferingIndicator)
|
||||
_chewieController?.bufferingBuilder?.call(context) ??
|
||||
const Center(
|
||||
child: EnteLoadingWidget(
|
||||
size: 32,
|
||||
color: fillBaseDark,
|
||||
padding: 0,
|
||||
),
|
||||
_buildBottomBar(context),
|
||||
],
|
||||
),
|
||||
)
|
||||
else
|
||||
_buildHitArea(),
|
||||
SafeArea(
|
||||
top: false,
|
||||
left: false,
|
||||
right: false,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
PreviewStatusWidget(
|
||||
showControls: !notifier.hideStuff,
|
||||
file: widget.file,
|
||||
isPreviewPlayer: true,
|
||||
onStreamChange: widget.onStreamChange,
|
||||
),
|
||||
if (!chewieController.isLive) _buildBottomBar(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -136,9 +135,9 @@ class _VideoControlsState extends State<VideoControls> {
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
final oldController = chewieController;
|
||||
chewieController = ChewieController.of(context);
|
||||
controller = chewieController!.videoPlayerController;
|
||||
final oldController = _chewieController;
|
||||
_chewieController = ChewieController.of(context);
|
||||
controller = chewieController.videoPlayerController;
|
||||
|
||||
if (oldController != chewieController) {
|
||||
_dispose();
|
||||
@ -148,35 +147,75 @@ class _VideoControlsState extends State<VideoControls> {
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
Widget _buildBottomBar(
|
||||
AnimatedOpacity _buildBottomBar(
|
||||
BuildContext context,
|
||||
) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(bottom: 60),
|
||||
height: 100,
|
||||
child: AnimatedOpacity(
|
||||
opacity: _hideStuff ? 0.0 : 1.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: _SeekBarAndDuration(
|
||||
controller: controller,
|
||||
latestValue: _latestValue,
|
||||
updateDragging: (bool value) {
|
||||
setState(() {
|
||||
_dragging = value;
|
||||
});
|
||||
},
|
||||
return AnimatedOpacity(
|
||||
opacity: notifier.hideStuff ? 0.0 : 1.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Container(
|
||||
height: 40,
|
||||
margin: const EdgeInsets.only(bottom: 60),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
16,
|
||||
4,
|
||||
16,
|
||||
4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
border: Border.all(
|
||||
color: strokeFaintDark,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
formatDuration(_latestValue.position),
|
||||
style: getEnteTextTheme(
|
||||
context,
|
||||
).mini.copyWith(
|
||||
color: textBaseDark,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: _buildProgressBar(),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
formatDuration(
|
||||
_latestValue.duration,
|
||||
),
|
||||
style: getEnteTextTheme(
|
||||
context,
|
||||
).mini.copyWith(
|
||||
color: textBaseDark,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHitArea() {
|
||||
final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
||||
_latestValue.duration.inSeconds > 0;
|
||||
final bool showPlayButton = true && !_dragging && !notifier.hideStuff;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (_latestValue != null) {
|
||||
if (_latestValue.isPlaying) {
|
||||
if (_displayTapped) {
|
||||
setState(() {
|
||||
_hideStuff = !_hideStuff;
|
||||
notifier.hideStuff = true;
|
||||
});
|
||||
} else {
|
||||
_cancelAndRestartTimer();
|
||||
@ -185,20 +224,32 @@ class _VideoControlsState extends State<VideoControls> {
|
||||
_playPause();
|
||||
|
||||
setState(() {
|
||||
_hideStuff = true;
|
||||
notifier.hideStuff = true;
|
||||
});
|
||||
}
|
||||
widget.playbackCallback?.call(_hideStuff);
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: AnimatedOpacity(
|
||||
opacity: _latestValue != null && !_hideStuff && !_dragging ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Center(
|
||||
child: _PlayPauseButton(
|
||||
_playPause,
|
||||
_latestValue!.isPlaying,
|
||||
),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
color: Colors
|
||||
.transparent, // The Gesture Detector doesn't expand to the full size of the container without this; Not sure why!
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(
|
||||
horizontal: marginSize,
|
||||
),
|
||||
child: CenterPlayButton(
|
||||
backgroundColor: Colors.black54,
|
||||
iconColor: Colors.white,
|
||||
isFinished: isFinished,
|
||||
isPlaying: controller.value.isPlaying,
|
||||
show: showPlayButton,
|
||||
onPressed: _playPause,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -209,10 +260,10 @@ class _VideoControlsState extends State<VideoControls> {
|
||||
_startHideTimer();
|
||||
|
||||
setState(() {
|
||||
_hideStuff = false;
|
||||
notifier.hideStuff = false;
|
||||
_displayTapped = true;
|
||||
});
|
||||
widget.playbackCallback?.call(_hideStuff);
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
}
|
||||
|
||||
Future<void> _initialize() async {
|
||||
@ -220,26 +271,28 @@ class _VideoControlsState extends State<VideoControls> {
|
||||
|
||||
_updateState();
|
||||
|
||||
if ((controller.value.isPlaying) || chewieController!.autoPlay) {
|
||||
if (controller.value.isPlaying || chewieController.autoPlay) {
|
||||
_startHideTimer();
|
||||
}
|
||||
|
||||
if (chewieController!.showControlsOnInitialize) {
|
||||
if (chewieController.showControlsOnInitialize) {
|
||||
_initTimer = Timer(const Duration(milliseconds: 200), () {
|
||||
setState(() {
|
||||
_hideStuff = false;
|
||||
notifier.hideStuff = false;
|
||||
});
|
||||
widget.playbackCallback?.call(_hideStuff);
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _playPause() {
|
||||
final bool isFinished = _latestValue!.position >= _latestValue!.duration;
|
||||
final bool isFinished = (_latestValue.position >= _latestValue.duration) &&
|
||||
_latestValue.duration.inSeconds > 0;
|
||||
|
||||
setState(() {
|
||||
if (controller.value.isPlaying) {
|
||||
_hideStuff = false;
|
||||
notifier.hideStuff = false;
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
_hideTimer?.cancel();
|
||||
controller.pause();
|
||||
} else {
|
||||
@ -251,30 +304,41 @@ class _VideoControlsState extends State<VideoControls> {
|
||||
});
|
||||
} else {
|
||||
if (isFinished) {
|
||||
controller.seekTo(const Duration(seconds: 0));
|
||||
controller.seekTo(Duration.zero);
|
||||
}
|
||||
controller.play();
|
||||
}
|
||||
}
|
||||
widget.playbackCallback?.call(_hideStuff);
|
||||
});
|
||||
}
|
||||
|
||||
void _startHideTimer() {
|
||||
_hideTimer = Timer(const Duration(seconds: 2), () {
|
||||
final hideControlsTimer = chewieController.hideControlsTimer.isNegative
|
||||
? ChewieController.defaultHideControlsTimer
|
||||
: chewieController.hideControlsTimer;
|
||||
_hideTimer = Timer(hideControlsTimer, () {
|
||||
setState(() {
|
||||
_hideStuff = true;
|
||||
notifier.hideStuff = true;
|
||||
widget.playbackCallback?.call(notifier.hideStuff);
|
||||
});
|
||||
widget.playbackCallback?.call(_hideStuff);
|
||||
});
|
||||
}
|
||||
|
||||
void _bufferingTimerTimeout() {
|
||||
_displayBufferingIndicator = true;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
void _updateState() {
|
||||
if (!mounted) return;
|
||||
|
||||
// display the progress bar indicator only after the buffering delay if it has been set
|
||||
if (chewieController?.progressIndicatorDelay != null) {
|
||||
if (chewieController.progressIndicatorDelay != null) {
|
||||
if (controller.value.isBuffering) {
|
||||
_bufferingDisplayTimer ??= Timer(
|
||||
chewieController!.progressIndicatorDelay!,
|
||||
chewieController.progressIndicatorDelay!,
|
||||
_bufferingTimerTimeout,
|
||||
);
|
||||
} else {
|
||||
@ -285,239 +349,104 @@ class _VideoControlsState extends State<VideoControls> {
|
||||
} else {
|
||||
_displayBufferingIndicator = controller.value.isBuffering;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_latestValue = controller.value;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildProgressBar() {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return Expanded(
|
||||
child: CustomProgressBar(
|
||||
controller,
|
||||
onDragStart: () {
|
||||
setState(() {
|
||||
_dragging = true;
|
||||
});
|
||||
|
||||
_hideTimer?.cancel();
|
||||
},
|
||||
onDragUpdate: () {
|
||||
_hideTimer?.cancel();
|
||||
},
|
||||
onDragEnd: () {
|
||||
setState(() {
|
||||
_dragging = false;
|
||||
});
|
||||
|
||||
_startHideTimer();
|
||||
},
|
||||
colors: ChewieProgressColors(
|
||||
playedColor: colorScheme.primary300,
|
||||
handleColor: backgroundElevatedLight,
|
||||
bufferedColor: backgroundElevatedLight.withOpacity(0.5),
|
||||
backgroundColor: fillMutedDark,
|
||||
),
|
||||
draggableProgressBar: chewieController.draggableProgressBar,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SeekBarAndDuration extends StatelessWidget {
|
||||
final VideoPlayerController? controller;
|
||||
final VideoPlayerValue? latestValue;
|
||||
final Function(bool) updateDragging;
|
||||
|
||||
const _SeekBarAndDuration({
|
||||
required this.controller,
|
||||
required this.latestValue,
|
||||
required this.updateDragging,
|
||||
class CenterPlayButton extends StatelessWidget {
|
||||
const CenterPlayButton({
|
||||
super.key,
|
||||
required this.backgroundColor,
|
||||
this.iconColor,
|
||||
required this.show,
|
||||
required this.isPlaying,
|
||||
required this.isFinished,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
final Color backgroundColor;
|
||||
final Color? iconColor;
|
||||
final bool show;
|
||||
final bool isPlaying;
|
||||
final bool isFinished;
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
),
|
||||
return AnimatedOpacity(
|
||||
opacity: show ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
16,
|
||||
4,
|
||||
16,
|
||||
4,
|
||||
),
|
||||
width: 54,
|
||||
height: 54,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8),
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: strokeFaintDark,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
if (latestValue?.position == null)
|
||||
Text(
|
||||
"0:00",
|
||||
style: getEnteTextTheme(
|
||||
context,
|
||||
).mini.copyWith(
|
||||
color: textBaseDark,
|
||||
),
|
||||
)
|
||||
else
|
||||
Text(
|
||||
_secondsToDuration(latestValue!.position.inSeconds),
|
||||
style: getEnteTextTheme(
|
||||
context,
|
||||
).mini.copyWith(
|
||||
color: textBaseDark,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _SeekBar(controller!, updateDragging),
|
||||
),
|
||||
Text(
|
||||
_secondsToDuration(
|
||||
latestValue?.duration.inSeconds ?? 0,
|
||||
),
|
||||
style: getEnteTextTheme(
|
||||
context,
|
||||
).mini.copyWith(
|
||||
color: textBaseDark,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onPressed,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(scale: animation, child: child);
|
||||
},
|
||||
switchInCurve: Curves.easeInOutQuart,
|
||||
switchOutCurve: Curves.easeInOutQuart,
|
||||
child: isPlaying
|
||||
? const Icon(
|
||||
Icons.pause,
|
||||
size: 32,
|
||||
key: ValueKey("pause"),
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Icon(
|
||||
Icons.play_arrow,
|
||||
size: 36,
|
||||
key: ValueKey("play"),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Returns the duration in the format "h:mm:ss" or "m:ss".
|
||||
String _secondsToDuration(int totalSeconds) {
|
||||
final hours = totalSeconds ~/ 3600;
|
||||
final minutes = (totalSeconds % 3600) ~/ 60;
|
||||
final seconds = totalSeconds % 60;
|
||||
|
||||
if (hours > 0) {
|
||||
return '${hours.toString().padLeft(1, '0')}:${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
} else {
|
||||
return '${minutes.toString().padLeft(1, '0')}:${seconds.toString().padLeft(2, '0')}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SeekBar extends StatefulWidget {
|
||||
final VideoPlayerController controller;
|
||||
final Function(bool) updateDragging;
|
||||
const _SeekBar(
|
||||
this.controller,
|
||||
this.updateDragging,
|
||||
);
|
||||
|
||||
@override
|
||||
State<_SeekBar> createState() => _SeekBarState();
|
||||
}
|
||||
|
||||
class _SeekBarState extends State<_SeekBar> {
|
||||
double _sliderValue = 0.0;
|
||||
final _debouncer = Debouncer(
|
||||
const Duration(milliseconds: 300),
|
||||
executionInterval: const Duration(milliseconds: 300),
|
||||
);
|
||||
bool _controllerWasPlaying = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.controller.addListener(updateSlider);
|
||||
}
|
||||
|
||||
void updateSlider() {
|
||||
if (widget.controller.value.isInitialized) {
|
||||
setState(() {
|
||||
_sliderValue = widget.controller.value.position.inSeconds.toDouble();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debouncer.cancelDebounceTimer();
|
||||
widget.controller.removeListener(updateSlider);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
return SliderTheme(
|
||||
data: SliderTheme.of(context).copyWith(
|
||||
trackHeight: 1.0,
|
||||
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0),
|
||||
overlayShape: const RoundSliderOverlayShape(overlayRadius: 14.0),
|
||||
activeTrackColor: colorScheme.primary300,
|
||||
inactiveTrackColor: fillMutedDark,
|
||||
thumbColor: backgroundElevatedLight,
|
||||
overlayColor: fillMutedDark,
|
||||
),
|
||||
child: Slider(
|
||||
min: 0.0,
|
||||
max: widget.controller.value.duration.inSeconds.toDouble(),
|
||||
value: _sliderValue,
|
||||
onChangeStart: (value) async {
|
||||
widget.updateDragging(true);
|
||||
_controllerWasPlaying = widget.controller.value.isPlaying;
|
||||
if (_controllerWasPlaying) {
|
||||
await widget.controller.pause();
|
||||
}
|
||||
},
|
||||
onChanged: (value) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_sliderValue = value;
|
||||
});
|
||||
}
|
||||
|
||||
_debouncer.run(() async {
|
||||
await widget.controller.seekTo(Duration(seconds: value.toInt()));
|
||||
});
|
||||
},
|
||||
divisions: 4500,
|
||||
onChangeEnd: (value) async {
|
||||
await widget.controller.seekTo(Duration(seconds: value.toInt()));
|
||||
|
||||
if (_controllerWasPlaying) {
|
||||
await widget.controller.play();
|
||||
}
|
||||
widget.updateDragging(false);
|
||||
},
|
||||
allowedInteraction: SliderInteraction.tapAndSlide,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PlayPauseButton extends StatefulWidget {
|
||||
final void Function() playPause;
|
||||
final bool isPlaying;
|
||||
const _PlayPauseButton(
|
||||
this.playPause,
|
||||
this.isPlaying,
|
||||
);
|
||||
|
||||
@override
|
||||
State<_PlayPauseButton> createState() => _PlayPauseButtonState();
|
||||
}
|
||||
|
||||
class _PlayPauseButtonState extends State<_PlayPauseButton> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 54,
|
||||
height: 54,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withOpacity(0.3),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: strokeFaintDark,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: widget.playPause,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(scale: animation, child: child);
|
||||
},
|
||||
switchInCurve: Curves.easeInOutQuart,
|
||||
switchOutCurve: Curves.easeInOutQuart,
|
||||
child: widget.isPlaying
|
||||
? const Icon(
|
||||
Icons.pause,
|
||||
size: 32,
|
||||
key: ValueKey("pause"),
|
||||
color: Colors.white,
|
||||
)
|
||||
: const Icon(
|
||||
Icons.play_arrow,
|
||||
size: 36,
|
||||
key: ValueKey("play"),
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -0,0 +1,41 @@
|
||||
// ignore_for_file: implementation_imports
|
||||
|
||||
import "package:chewie/src/chewie_progress_colors.dart";
|
||||
import "package:chewie/src/progress_bar.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/widgets.dart";
|
||||
import "package:video_player/video_player.dart";
|
||||
|
||||
class CustomProgressBar extends StatelessWidget {
|
||||
CustomProgressBar(
|
||||
this.controller, {
|
||||
ChewieProgressColors? colors,
|
||||
this.onDragEnd,
|
||||
this.onDragStart,
|
||||
this.onDragUpdate,
|
||||
super.key,
|
||||
this.draggableProgressBar = true,
|
||||
}) : colors = colors ?? ChewieProgressColors();
|
||||
|
||||
final VideoPlayerController controller;
|
||||
final ChewieProgressColors colors;
|
||||
final Function()? onDragStart;
|
||||
final Function()? onDragEnd;
|
||||
final Function()? onDragUpdate;
|
||||
final bool draggableProgressBar;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return VideoProgressBar(
|
||||
controller,
|
||||
barHeight: 1.5,
|
||||
handleHeight: 8,
|
||||
drawShadow: true,
|
||||
colors: colors,
|
||||
onDragEnd: onDragEnd,
|
||||
onDragStart: onDragStart,
|
||||
onDragUpdate: onDragUpdate,
|
||||
draggableProgressBar: draggableProgressBar,
|
||||
);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user