lib/ui/aliplayer_video_slider.dart (149 lines of code) (raw):

// Copyright © 2025 Alibaba Cloud. All rights reserved. // // Author: keria // Date: 2025/2/12 // Brief: 播放器进度条控件 import 'package:aliplayer_widget/utils/format_util.dart'; import 'package:aliplayer_widget/utils/vibration_util.dart'; import 'package:flutter/material.dart'; /// 播放器进度条控件,用于显示视频播放进度和缓冲进度 /// /// 提供拖拽功能以调整播放位置,并支持边界振动反馈 class AliPlayerVideoSlider extends StatefulWidget { /// 当前播放位置 final Duration currentPosition; /// 视频总时长 final Duration totalDuration; /// 已缓冲的位置 final Duration bufferedPosition; /// 拖拽过程中触发的回调 final ValueChanged<Duration>? onDragUpdate; /// 拖拽结束时触发的回调 final ValueChanged<Duration>? onDragEnd; /// seek结束时触发的回调 final ValueChanged<Duration>? onSeekEnd; const AliPlayerVideoSlider({ super.key, required this.currentPosition, required this.totalDuration, required this.bufferedPosition, this.onDragUpdate, this.onDragEnd, this.onSeekEnd, }); @override State<AliPlayerVideoSlider> createState() => _AliPlayerVideoSliderState(); } class _AliPlayerVideoSliderState extends State<AliPlayerVideoSlider> { // 初始化颜色管理 static const Map<String, Color> _colors = { 'background': Colors.grey, 'buffered': Colors.blueGrey, 'progress': Colors.blueAccent, 'thumb': Colors.blue, }; // 当前拖拽的进度百分比 double _dragPositionPercent = 0.0; // 是否正在拖拽 bool _isDragging = false; // 缓存 SliderThemeData late SliderThemeData _sliderThemeData; // 缓存计算结果 late int _totalDurationInMillis; late int _currentPositionInMillis; late int _bufferedPositionInMillis; @override void initState() { super.initState(); _updateCachedValues(); } /// 更新缓存的毫秒数值 void _updateCachedValues() { _totalDurationInMillis = widget.totalDuration.inMilliseconds; _currentPositionInMillis = widget.currentPosition.inMilliseconds; _bufferedPositionInMillis = widget.bufferedPosition.inMilliseconds; } /// 构建播放器视图 /// /// Builds the main player view. @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 4.0, vertical: 2.0), child: Row( children: [ _buildVideoSlider(), const SizedBox(width: 8), _buildTimeText(), ], ), ); } /// 构建视频进度条 Widget _buildVideoSlider() { // 预计算进度百分比和缓冲百分比 final progressPercent = _totalDurationInMillis > 0 ? (_currentPositionInMillis / _totalDurationInMillis).clamp(0.0, 1.0) : 0.0; final bufferPercent = _totalDurationInMillis > 0 ? (_bufferedPositionInMillis / _totalDurationInMillis).clamp(0.0, 1.0) : 0.0; return Expanded( child: SliderTheme( data: _sliderThemeData, // 使用缓存的 SliderThemeData child: Slider( value: _isDragging ? _dragPositionPercent : progressPercent, min: 0.0, max: 1.0, secondaryTrackValue: bufferPercent, onChanged: (value) => _updateDragPosition(value), onChangeEnd: (value) => _endDrag(value), ), ), ); } /// 获取 SliderTheme 数据 SliderThemeData _getSliderThemeData(BuildContext context) { return SliderTheme.of(context).copyWith( trackHeight: 2.0, thumbShape: const RoundSliderThumbShape( enabledThumbRadius: 4.0, elevation: 2.0, ), overlayShape: const RoundSliderOverlayShape( overlayRadius: 8.0, ), overlayColor: _colors['progress']!.withAlpha(32), activeTrackColor: _colors['progress']!, thumbColor: _colors['thumb']!, inactiveTrackColor: _colors['background']!, secondaryActiveTrackColor: _colors['buffered']!, ); } /// 构建时间文本 Widget _buildTimeText() { return Text( '${FormatUtil.formatDuration(widget.currentPosition)} / ${FormatUtil.formatDuration(widget.totalDuration)}', style: const TextStyle( fontSize: 12, color: Colors.white, ), ); } /// 更新拖拽位置并触发回调 void _updateDragPosition(double value) { // 如果 onDragUpdate 回调为空,则不响应拖拽操作 if (widget.onDragUpdate == null) return; final lastValue = _dragPositionPercent; setState(() { _isDragging = true; _dragPositionPercent = value; }); if ((value - lastValue).abs() > 0.05) { VibrationUtil.vibrateOnEdge(value); // Vibrate on significant change } // Calculate time in milliseconds final dragTimeInMillis = (value * _totalDurationInMillis).toInt(); widget.onDragUpdate?.call(Duration(milliseconds: dragTimeInMillis)); } /// 结束拖拽并触发回调 void _endDrag(double value) { // 如果 onDragEnd 或 onSeekEnd 回调为空,则不响应拖拽结束操作 if (widget.onDragEnd == null && widget.onSeekEnd == null) return; setState(() { _isDragging = false; }); VibrationUtil.vibrateOnEdge(value); // Calculate time in milliseconds final dragTimeInMillis = (value * _totalDurationInMillis).toInt(); widget.onDragEnd?.call(Duration(milliseconds: dragTimeInMillis)); widget.onSeekEnd?.call(Duration(milliseconds: dragTimeInMillis)); } /// 状态更新回调 /// 当 Widget 的状态被更新时,该方法被调用。 /// /// Called when the state of the widget is updated. @override void didUpdateWidget(covariant AliPlayerVideoSlider oldWidget) { super.didUpdateWidget(oldWidget); // 比较秒级差异,只有当秒级发生变化时才更新缓存 final oldTotalSeconds = oldWidget.totalDuration.inSeconds; final newTotalSeconds = widget.totalDuration.inSeconds; final oldCurrentSeconds = oldWidget.currentPosition.inSeconds; final newCurrentSeconds = widget.currentPosition.inSeconds; final oldBufferedSeconds = oldWidget.bufferedPosition.inSeconds; final newBufferedSeconds = widget.bufferedPosition.inSeconds; if (oldTotalSeconds != newTotalSeconds || oldCurrentSeconds != newCurrentSeconds || oldBufferedSeconds != newBufferedSeconds) { _updateCachedValues(); } } @override void didChangeDependencies() { super.didChangeDependencies(); // 初始化并缓存 SliderThemeData _sliderThemeData = _getSliderThemeData(context); } }