refactor progress bar

Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-06-26 12:45:41 +08:00
parent 2ba6f0d063
commit a623ceee47
12 changed files with 263 additions and 391 deletions

View File

@@ -72,38 +72,31 @@ class PlPlayerController with BlockConfigMixin {
Player? _videoPlayerController;
VideoController? _videoController;
// 添加一个私有静态变量来保存实例
static PlPlayerController? _instance;
// 流事件 监听播放状态变化
// StreamSubscription? _playerEventSubs;
final playerStatus = PlPlayerStatus(.playing);
/// [playerStatus] has a [status] observable
final playerStatus = PlPlayerStatus(PlayerStatus.playing);
final Rx<DataStatus> dataStatus = Rx(.none);
///
final Rx<DataStatus> dataStatus = Rx(DataStatus.none);
Duration? seekToPos;
bool hasToasted = false;
final RxBool isSeeking = false.obs;
// bool controlsEnabled = false;
final RxInt position = RxInt(0);
/// 响应数据
/// 带有Seconds的变量只在秒数更新时更新以避免频繁触发重绘
// 播放位置
Duration position = Duration.zero;
final RxInt positionSeconds = 0.obs;
int get positionInMilliseconds =>
videoPlayerController?.state.position.inMilliseconds ?? 0;
/// 进度条位置
Duration sliderPosition = Duration.zero;
final RxInt sliderPositionSeconds = 0.obs;
// 展示使用
final Rx<Duration> sliderTempPosition = Rx(Duration.zero);
final RxInt buffered = RxInt(0);
/// 视频时长
final Rx<Duration> duration = Rx(Duration.zero);
final RxInt duration = RxInt(0);
/// 视频缓冲
final Rx<Duration> buffered = Rx(Duration.zero);
final RxInt bufferedSeconds = 0.obs;
int durationInMilliseconds = 0;
void updateDuration(Duration value) {
duration.value = value.inSeconds;
durationInMilliseconds = value.inMilliseconds;
}
int _playerCount = 0;
@@ -111,44 +104,31 @@ class PlPlayerController with BlockConfigMixin {
final RxDouble _playbackSpeed = Pref.playSpeedDefault.obs;
late final RxDouble _longPressSpeed = Pref.longPressSpeedDefault.obs;
/// 音量控制条
final RxDouble volume = RxDouble(
PlatformUtils.isDesktop ? Pref.desktopVolume : 1.0,
);
final setSystemBrightness = Pref.setSystemBrightness;
/// 亮度控制条
final RxDouble brightness = (-1.0).obs;
/// 是否展示控制条
final RxBool showControls = false.obs;
/// 亮度控制条展示/隐藏
final RxBool showBrightnessStatus = false.obs;
/// 是否长按倍速
final RxBool longPressStatus = false.obs;
/// 屏幕锁 为true时关闭控制栏
final RxBool controlsLock = false.obs;
/// 全屏状态
final RxBool isFullScreen = false.obs;
// 默认投稿视频格式
bool isLive = false;
bool _isVertical = false;
/// 视频比例
final Rx<VideoFitType> videoFit = Rx(VideoFitType.contain);
final Rx<VideoFitType> videoFit = Rx(.contain);
/// 后台播放
late final RxBool continuePlayInBackground =
Pref.continuePlayInBackground.obs;
///
final RxBool isSliderMoving = false.obs;
bool _autoPlay = false;
// 记录历史记录
@@ -168,7 +148,7 @@ class PlPlayerController with BlockConfigMixin {
late DataSource dataSource;
Timer? _timer;
StreamSubscription<Duration>? _subForSeek;
StreamSubscription? _subForSeek;
Box setting = GStorage.setting;
@@ -387,8 +367,7 @@ class PlPlayerController with BlockConfigMixin {
? Pref.sliderDuration / 100
: Pref.sliderDuration * 1000;
num get sliderScale =>
isRelative ? duration.value.inMilliseconds * offset : offset;
num get sliderScale => isRelative ? durationInMilliseconds * offset : offset;
// 播放顺序相关
late PlayRepeat playRepeat = Pref.playRepeat;
@@ -441,27 +420,6 @@ class PlPlayerController with BlockConfigMixin {
putSubtitleSettings();
}
void updateSliderPositionSecond() {
int newSecond = sliderPosition.inSeconds;
if (sliderPositionSeconds.value != newSecond) {
sliderPositionSeconds.value = newSecond;
}
}
void updatePositionSecond() {
int newSecond = position.inSeconds;
if (positionSeconds.value != newSecond) {
positionSeconds.value = newSecond;
}
}
void updateBufferedSecond() {
int newSecond = buffered.value.inSeconds;
if (bufferedSeconds.value != newSecond) {
bufferedSeconds.value = newSecond;
}
}
static PlPlayerController? get instance => _instance;
static bool instanceExists() {
@@ -475,7 +433,6 @@ class PlPlayerController with BlockConfigMixin {
static PlayCallback? _playCallBack;
static Future<void>? playIfExists() {
// await _instance?.play(repeat: repeat, hideControls: hideControls);
return _playCallBack?.call();
}
@@ -688,14 +645,10 @@ class PlPlayerController with BlockConfigMixin {
return;
}
// 获取视频时长 00:00
this.duration.value = duration ?? _videoPlayerController!.state.duration;
position = buffered.value = sliderPosition = seekTo ?? Duration.zero;
updatePositionSecond();
updateSliderPositionSecond();
updateBufferedSecond();
// 数据加载完成
dataStatus.value = DataStatus.loaded;
updateDuration(duration ?? _videoPlayerController!.state.duration);
position.value = buffered.value = seekTo?.inSeconds ?? 0;
dataStatus.value = .loaded;
if (autoFullScreenFlag && autoEnterFullScreen) {
triggerFullScreen(status: true);
@@ -821,10 +774,7 @@ class PlPlayerController with BlockConfigMixin {
Volume? volume,
) async {
isBuffering.value = false;
buffered.value = Duration.zero;
_heartDuration = 0;
position = Duration.zero;
// 初始化时清空弹幕,防止上次重叠
danmakuController?.clear();
var player = _videoPlayerController;
@@ -906,7 +856,10 @@ class PlPlayerController with BlockConfigMixin {
return null;
}
if (_videoPlayerController case final ctr? when (ctr.current.isNotEmpty)) {
return ctr.open(ctr.current.last.copyWith(start: position), play: true);
return ctr.open(
ctr.current.last.copyWith(start: ctr.state.position),
play: true,
);
}
return null;
}
@@ -948,9 +901,10 @@ class PlPlayerController with BlockConfigMixin {
assert(_subscriptions == null);
final stream = player.stream;
_subscriptions = [
stream.playing.listen((event) {
WakelockPlus.toggle(enable: event);
if (event) {
/// playing
stream.playing.listen((bool playing) {
WakelockPlus.toggle(enable: playing);
if (playing) {
if (_isAutoEnterPip) {
if (_isCurrVideoPage) {
enterPip(autoEnter: true);
@@ -958,64 +912,68 @@ class PlPlayerController with BlockConfigMixin {
_disableAutoEnterPip();
}
}
playerStatus.value = PlayerStatus.playing;
playerStatus.value = .playing;
} else {
_disableAutoEnterPip();
playerStatus.value = PlayerStatus.paused;
playerStatus.value = .paused;
}
videoPlayerServiceHandler?.onStatusChange(
playerStatus.value,
isBuffering.value,
isLive,
);
/// 触发回调事件
for (final element in _statusListeners) {
element(event ? PlayerStatus.playing : PlayerStatus.paused);
element(playing ? .playing : .paused);
}
if (videoPlayerController!.state.position.inSeconds != 0) {
makeHeartBeat(positionSeconds.value, type: HeartBeatType.status);
final seconds = videoPlayerController!.state.position.inSeconds;
if (seconds != 0) {
makeHeartBeat(seconds, type: .status);
}
}),
stream.completed.listen((event) {
if (event) {
playerStatus.value = PlayerStatus.completed;
/// 触发回调事件
///completed
stream.completed.listen((bool completed) {
if (completed) {
playerStatus.value = .completed;
for (final element in _statusListeners) {
element(PlayerStatus.completed);
element(.completed);
}
} else {
// playerStatus.value = PlayerStatus.playing;
makeHeartBeat(-1, type: .completed);
}
makeHeartBeat(positionSeconds.value, type: HeartBeatType.completed);
}),
stream.position.listen((event) {
position = event;
updatePositionSecond();
if (!isSliderMoving.value) {
sliderPosition = event;
updateSliderPositionSecond();
/// position
stream.position.listen((Duration position) {
final posInSeconds = position.inSeconds;
if (posInSeconds != this.position.value) {
if (!isSeeking.value) {
this.position.value = posInSeconds;
}
videoPlayerServiceHandler?.onPositionChange(position);
}
/// 触发回调事件
for (final element in _positionListeners) {
element(event);
element(position);
}
makeHeartBeat(event.inSeconds);
makeHeartBeat(posInSeconds);
}),
stream.duration.listen((Duration event) {
duration.value = event;
stream.duration.listen(updateDuration),
stream.buffer.listen((Duration buffer) {
buffered.value = buffer.inSeconds;
}),
stream.buffer.listen((Duration event) {
buffered.value = event;
updateBufferedSecond();
}),
stream.buffering.listen((bool event) {
isBuffering.value = event;
stream.buffering.listen((bool buffering) {
isBuffering.value = buffering;
videoPlayerServiceHandler?.onStatusChange(
playerStatus.value,
event,
buffering,
isLive,
);
}),
@@ -1056,7 +1014,7 @@ class PlPlayerController with BlockConfigMixin {
// if (kDebugMode) {
// debugPrint("_buffered.value: ${_buffered.value}");
// }
if (isBuffering.value && buffered.value == Duration.zero) {
if (isBuffering.value && buffered.value == 0) {
SmartDialog.showToast(
'视频链接打开失败,重试中',
displayTime: const Duration(milliseconds: 500),
@@ -1079,16 +1037,6 @@ class PlPlayerController with BlockConfigMixin {
// SmartDialog.showToast('视频加载错误, $event');
}
}),
// controllerStream.volume.listen((event) {
// if (!mute.value && _volumeBeforeMute != event) {
// _volumeBeforeMute = event / 100;
// }
// }),
// 媒体通知监听
if (videoPlayerServiceHandler != null)
positionSeconds.listen((int event) {
videoPlayerServiceHandler!.onPositionChange(Duration(seconds: event));
}),
];
}
@@ -1108,17 +1056,12 @@ class PlPlayerController with BlockConfigMixin {
/// 跳转至指定位置
Future<void> seekTo(Duration position, {bool isSeek = true}) async {
// if (position >= duration.value) {
// position = duration.value - const Duration(milliseconds: 100);
// }
if (_playerCount == 0) {
return;
}
if (position < Duration.zero) {
position = Duration.zero;
}
this.position = position;
updatePositionSecond();
_heartDuration = position.inSeconds;
Future<void> seek() async {
@@ -1134,7 +1077,7 @@ class PlPlayerController with BlockConfigMixin {
}
}
if (duration.value != Duration.zero) {
if (duration.value != 0) {
seek();
} else {
// if (kDebugMode) debugPrint('seek duration else');
@@ -1214,42 +1157,22 @@ class PlPlayerController with BlockConfigMixin {
void hideTaskControls() {
_timer?.cancel();
_timer = Timer(showControlDuration, () {
if (!isSliderMoving.value && !tripling) {
if (!isSeeking.value && !tripling) {
controls = false;
}
_timer = null;
});
}
/// 调整播放时间
void onChangedSlider(int v) {
sliderPosition = Duration(seconds: v);
updateSliderPositionSecond();
}
void onChangedSliderStart([Duration? value]) {
if (value != null) {
sliderTempPosition.value = value;
}
isSliderMoving.value = true;
}
bool? cancelSeek;
bool? hasToast;
void onUpdatedSliderProgress(Duration value) {
sliderTempPosition.value = value;
sliderPosition = value;
updateSliderPositionSecond();
}
void onChangedSliderEnd() {
if (cancelSeek != true) {
void onSeekEnd() {
if (seekToPos != null) {
feedBack();
}
cancelSeek = null;
hasToast = null;
isSliderMoving.value = false;
if (showSeekPreview) {
showPreview.value = false;
}
hasToasted = false;
isSeeking.value = false;
hideTaskControls();
}
@@ -1350,13 +1273,9 @@ class PlPlayerController with BlockConfigMixin {
}
}
bool get _isCompleted =>
videoPlayerController!.state.completed ||
(duration.value - position).inMilliseconds <= 50;
// 双击播放、暂停
Future<void> onDoubleTapCenter() async {
if (!isLive && _isCompleted) {
if (!isLive && videoPlayerController!.state.completed) {
await videoPlayerController!.seek(Duration.zero);
videoPlayerController!.play();
} else {
@@ -1376,11 +1295,11 @@ class PlPlayerController with BlockConfigMixin {
}
void onForward(Duration duration) {
onForwardBackward(position + duration);
onForwardBackward(videoPlayerController!.state.position + duration);
}
void onBackward(Duration duration) {
onForwardBackward(position - duration);
onForwardBackward(videoPlayerController!.state.position - duration);
}
void onForwardBackward(Duration duration) {
@@ -1568,7 +1487,7 @@ class PlPlayerController with BlockConfigMixin {
}
case .completed:
if (playerStatus.isCompleted &&
(duration.value - position).inMilliseconds <= 1000) {
(durationInMilliseconds - positionInMilliseconds) <= 1000) {
progress = -1;
}
return send();
@@ -1734,7 +1653,7 @@ class PlPlayerController with BlockConfigMixin {
Future<void> takeScreenshot() async {
SmartDialog.showToast('截图中');
final time = DurationUtils.formatDuration(
position.inMilliseconds / 1000,
positionInMilliseconds / 1000,
).replaceAll(':', '-');
final image = await videoPlayerController?.screenshot();
if (image != null) {