mangayomi-mirror/lib/modules/anime/widgets/custom_seekbar.dart
NBA2K1 1e0951be42 Fix Slider assertion error when skipping past video duration
Prevent an AssertionError in the Slider widget when the skip duration
button causes the value to exceed the video's maximum duration.
Clamp the Slider's value to the range [0, max]
using `clamp(0.0, maxValue).toDouble()` to ensure it stays within bounds.

Exception example:
```
Exception has occurred.
_AssertionError ('package:flutter/src/material/slider.dart': Failed assertion: line 199 pos 10: 'value >= min && value <= max': Value 1410000.0 is not between minimum 0.0 and maximum 1409533.0)
```
2025-05-17 21:44:12 +02:00

139 lines
4 KiB
Dart

import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video_controls/src/controls/extensions/duration.dart';
class CustomSeekBar extends StatefulWidget {
final Player player;
final Duration? delta;
final Function(Duration)? onSeekStart;
final Function(Duration)? onSeekEnd;
const CustomSeekBar({
super.key,
this.onSeekStart,
this.onSeekEnd,
required this.player,
this.delta,
});
@override
CustomSeekBarState createState() => CustomSeekBarState();
}
class CustomSeekBarState extends State<CustomSeekBar> {
Duration? tempPosition;
late Player player = widget.player;
Duration position = Duration.zero;
late Duration duration = player.state.duration;
Duration buffer = Duration.zero;
@override
void initState() {
super.initState();
player.stream.position.listen((event) {
if (mounted) {
setState(() {
position = event;
});
}
});
player.stream.duration.listen((event) {
if (mounted) {
setState(() {
duration = event;
});
}
});
player.stream.buffer.listen((event) {
if (mounted) {
setState(() {
buffer = event;
});
}
});
position = player.state.position;
duration = player.state.duration;
buffer = player.state.buffer;
}
final isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux;
@override
Widget build(BuildContext context) {
final maxValue = max(duration.inMilliseconds.toDouble(), 0).toDouble();
final rawValue =
(widget.delta ?? tempPosition ?? position).inMilliseconds.toDouble();
final clampedValue = rawValue.clamp(0, maxValue).toDouble();
return SizedBox(
height: 20,
child: Row(
children: [
if (!isDesktop)
SizedBox(
width: 70,
child: Center(
child: Text(
(widget.delta ?? tempPosition ?? position).label(
reference: duration,
),
style: const TextStyle(
height: 1.0,
fontSize: 12.0,
color: Colors.white,
),
),
),
),
Expanded(
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: isDesktop ? null : 3,
overlayShape: const RoundSliderOverlayShape(overlayRadius: 5.0),
),
child: Slider(
max: maxValue,
value: clampedValue,
secondaryTrackValue: max(buffer.inMilliseconds.toDouble(), 0),
onChanged: (value) {
widget.onSeekStart?.call(
Duration(
milliseconds: value.toInt() - position.inMilliseconds,
),
);
if (mounted) {
setState(() {
tempPosition = Duration(milliseconds: value.toInt());
});
}
},
onChangeEnd: (value) async {
widget.onSeekEnd?.call(
Duration(
milliseconds: value.toInt() - position.inMilliseconds,
),
);
widget.player.seek(Duration(milliseconds: value.toInt()));
},
),
),
),
if (!isDesktop)
SizedBox(
width: 70,
child: Center(
child: Text(
duration.label(reference: duration),
style: const TextStyle(
height: 1.0,
fontSize: 12.0,
color: Colors.white,
),
),
),
),
],
),
);
}
}