Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
@@ -0,0 +1,167 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../../../core/infra/player_engine/player_engine.dart';
import '../../../shared/utils/format_utils.dart';
import '../../../shared/widgets/app_loading_ring.dart';
import '../services/network_speed_monitor.dart';
class BufferingOverlay extends StatefulWidget {
final PlayerEngine player;
final NetworkSpeedMonitor? speedMonitor;
final ValueChanged<bool>? onBufferingChanged;
final bool suppress;
const BufferingOverlay({
super.key,
required this.player,
this.speedMonitor,
this.onBufferingChanged,
this.suppress = false,
});
@override
State<BufferingOverlay> createState() => _BufferingOverlayState();
}
class _BufferingOverlayState extends State<BufferingOverlay> {
bool _isBuffering = false;
Timer? _debounceTimer;
Duration _position = Duration.zero;
Duration _buffer = Duration.zero;
bool _playing = false;
StreamSubscription<Duration>? _bufferSub;
StreamSubscription<Duration>? _positionSub;
StreamSubscription<bool>? _playingSub;
@override
void initState() {
super.initState();
_readState(widget.player);
_subscribe(widget.player);
}
@override
void didUpdateWidget(covariant BufferingOverlay oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.player != widget.player) {
_cancelSubscriptions();
_setBuffering(false);
_readState(widget.player);
_subscribe(widget.player);
}
}
@override
void dispose() {
_debounceTimer?.cancel();
_cancelSubscriptions();
super.dispose();
}
void _readState(PlayerEngine player) {
_position = player.state.position;
_buffer = player.state.buffer;
_playing = player.state.playing;
}
void _subscribe(PlayerEngine player) {
_positionSub = player.stream.position.listen((p) {
_position = p;
_evaluate();
});
_bufferSub = player.stream.buffer.listen((b) {
_buffer = b;
_evaluate();
});
_playingSub = player.stream.playing.listen((playing) {
_playing = playing;
_evaluate();
});
}
void _cancelSubscriptions() {
unawaited(_positionSub?.cancel());
unawaited(_bufferSub?.cancel());
unawaited(_playingSub?.cancel());
_positionSub = null;
_bufferSub = null;
_playingSub = null;
}
void _setBuffering(bool value) {
if (_isBuffering == value) return;
setState(() => _isBuffering = value);
widget.onBufferingChanged?.call(value);
}
void _evaluate() {
final shouldBuffer =
_playing && _buffer <= _position && _position > Duration.zero;
if (shouldBuffer) {
if (_isBuffering || _debounceTimer != null) return;
_debounceTimer = Timer(const Duration(milliseconds: 200), () {
_debounceTimer = null;
if (!mounted) return;
if (_playing && _buffer <= _position && _position > Duration.zero) {
_setBuffering(true);
}
});
return;
}
_debounceTimer?.cancel();
_debounceTimer = null;
_setBuffering(false);
}
@override
Widget build(BuildContext context) {
final monitor = widget.speedMonitor;
return IgnorePointer(
child: Opacity(
opacity: widget.suppress ? 0.0 : 1.0,
child: AnimatedOpacity(
opacity: _isBuffering ? 1.0 : 0.0,
duration: const Duration(milliseconds: 100),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const AppLoadingRing(size: 40, color: Colors.white),
if (monitor != null) ...[
const SizedBox(height: 12),
ValueListenableBuilder<bool>(
valueListenable: monitor.available,
builder: (_, available, _) {
if (!available) return const SizedBox.shrink();
return ValueListenableBuilder<double>(
valueListenable: monitor.speed,
builder: (_, speed, _) {
return Text(
formatDownloadSpeed(speed),
style: const TextStyle(
color: Colors.white70,
fontSize: 13,
fontFeatures: [FontFeature.tabularFigures()],
),
);
},
);
},
),
],
],
),
),
),
),
);
}
}