问题现象

在开发Flutter视频播放器时,遇到了一个非常有意思的现象:

  • 当前播放第3个片段,向后跳到6等不正常,会先跳到片段6,然后再跳到4
    这个现象非常奇怪?

问题代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 用户手动切换片段
void _switchToSegment(int index) {
// 切换视频,设置新的播放位置
await _processSegmentBoundaries(newSegment);
_controller!.play();
}

// 视频播放监听器
void _videoListener() {
// 检测是否播放到片段结尾
if (currentPosition >= adjustedEndTime) {
_handleNextSegment(); // 这里又会调用 _switchToSegment
}
}

调试发现的问题

当调用 _switchToSegment(index) 时:

  1. 向后跳(如3→6)
    • 调用 _switchToSegment(6)
    • 视频跳转到第6个片段的开始位置
    • _videoListener 检测当前播放位置,发现已经大于原片段的 adjustedEndTime
    • 那么则调用_handleNextSegment方法, 跳转到3的下一个片段4

核心问题_videoListener_switchToSegment 还没完全初始化完成时就开始工作了。

解决思路

问题的本质是竞态条件:用户手动切换和自动切换逻辑产生了冲突。
解决思路很简单:加一个标志位,让这两个操作互相隔离

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class VideoPlayerState extends State<VideoPlayer> {
// 添加标志位
bool _isSwitchingSegment = false;

void _videoListener() {
// 如果正在手动切换片段,忽略自动切换逻辑
if (_isSwitchingSegment) {
return;
}

// 原有的自动切换逻辑
if (currentPosition >= adjustedEndTime) {
_handleNextSegment();
}
}

Future<void> _switchToSegment(int index) async {
// 设置标志位
_isSwitchingSegment = true;

try {
// 执行切换逻辑
await _processSegmentBoundaries(newSegment);
setState(() {
_currentSegmentIndex = index;
_isPlaying = true;
});
_controller!.play();
} finally {
// 无论成功失败,都要重置标志位
_isSwitchingSegment = false;
}
}
}

总结

这个bug很好地展示了异步编程中的经典问题:竞态条件

  • 现象:看似简单的功能,却出现了方向性的差异
  • 本质:两个异步操作之间的时序冲突
  • 解决:通过状态标志位进行操作隔离
    这提醒我们在处理用户交互和自动逻辑并存的场景时,一定要考虑它们之间的相互影响,必要时需要加入状态管理来避免冲突。