Flutter audioplayers中player.seek()需热重启后才生效的问题
问题:首次启动Flutter音频播放器时seek失效,进度滑块重置为0
- 热重启后程序运行正常,但首次启动应用时,每次点击播放,进度滑块都会重置为0,尽管代码中已添加seek到当前时间的逻辑
- 日志显示:调用
player.resume()及之后瞬间,currentTime存储着正确进度,但播放器恢复播放、onAudioPositionChanged触发后,currentTime变回0,说明player.seek()在首次启动时未生效 - 暂不确定该问题是否在iOS/Android平台上都存在
相关代码
import 'package:pick_pro/main.dart'; import 'package:pick_pro/metronome.dart'; import 'package:audioplayers/audioplayers.dart'; const Color darkBlue = Color(0xFF000c24); const Color blueGreen = Color.fromARGB(255, 121, 207, 175); final TextEditingController _controller = TextEditingController(); final TextEditingController _speedController = TextEditingController(); final TextEditingController _hintController = TextEditingController(); class Playback extends StatefulWidget { @override PlaybackState createState() => PlaybackState(); } class PlaybackState extends State<Playback> { final player = AudioPlayer(); late Duration songLength; Duration currentTime = Duration.zero; double playbackSpeed = 1; String assetPath = 'assets/sounds/demo_song.mp3'; bool isPlaying = false; int bpm = 100; @override void initState() { super.initState(); player.setReleaseMode(ReleaseMode.LOOP); // Set defualt song player.setUrl(assetPath, isLocal: true); songLength = const Duration(seconds: 259); // Listeners for changes in player states player.onPlayerStateChanged.listen((state) { setState(() { isPlaying = state == PlayerState.PLAYING; }); }); player.onDurationChanged.listen((newSongLength) { setState(() { songLength = newSongLength; }); }); player.onAudioPositionChanged.listen((newCurrentTime) { setState(() { if (isPlaying) { currentTime = newCurrentTime; } }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text( 'PickPro', style: TextStyle( fontSize: 40.0, fontFamily: 'Caveat', ), ), centerTitle: true, backgroundColor: blueGreen, ), body: Container( color: darkBlue, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const SizedBox( height: 32, ), Text('Get Song Name from metadata', style: buttonText()), const SizedBox(height: 20), Slider( min: 0, max: songLength.inSeconds.toDouble(), value: currentTime.inSeconds.toDouble(), onChanged: (value) { currentTime = Duration(seconds: value.toInt()); setState(() { player.pause(); player.seek(currentTime); }); }), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( formatTime(currentTime), style: buttonText(), ), Text( formatTime(songLength), style: buttonText(), ), ], ), ), const SizedBox(height: 20), IconButton( icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.white), iconSize: 50, onPressed: () { if (isPlaying) { setState(() { player.pause(); }); } else { setState(() { player.resume(); print(formatTime(currentTime)); }); } }, ), const SizedBox(height: 20), Slider( min: 0.5, max: 1.5, value: playbackSpeed, onChanged: (value) { playbackSpeed = double.parse(value.toStringAsFixed(2)); player.pause(); player.setPlaybackRate(playbackSpeed); setState(() {}); }), Center( child: Text( 'Speed: ${playbackSpeed}x', style: buttonText(), ), ), const SizedBox( height: 30, ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row( mainAxisAlignment: MainAxisAlignment.start, children: [ SizedBox( width: 120, height: 30, child: TextField( textAlign: TextAlign.center, controller: _hintController, enabled: false, decoration: const InputDecoration( hintText: 'Song BPM:', hintStyle: TextStyle(color: blueGreen, fontSize: 24), filled: true, fillColor: Colors.transparent, border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, contentPadding: EdgeInsets.zero), cursorColor: Colors.white, onSubmitted: (String value) {})), SizedBox( width: 60.0, height: 30.0, child: TextField( textAlign: TextAlign.center, controller: _controller, keyboardType: TextInputType.number, style: const TextStyle( color: blueGreen, fontSize: 24.0, ), decoration: InputDecoration( hintText: '${bpm}', hintStyle: const TextStyle(color: blueGreen, fontSize: 24), alignLabelWithHint: true, filled: true, fillColor: Colors.transparent, border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, contentPadding: EdgeInsets.zero), cursorColor: Colors.white, onSubmitted: (String value) { _controller.clear(); try { bpm = int.parse(value); setState(() {}); } catch (e) { ErrorPopup.show( context, "The BPM you entered is invalid."); } }, ), ), ], ), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ SizedBox( width: 145, height: 30, child: TextField( textAlign: TextAlign.center, controller: _hintController, enabled: false, decoration: const InputDecoration( hintText: 'Desired BPM:', hintStyle: TextStyle(color: blueGreen, fontSize: 24), filled: true, fillColor: Colors.transparent, border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, contentPadding: EdgeInsets.zero), cursorColor: Colors.white, onSubmitted: (String value) {})), SizedBox( width: 60.0, height: 30.0, child: TextField( textAlign: TextAlign.center, controller: _speedController, keyboardType: TextInputType.number, style: const TextStyle( color: blueGreen, fontSize: 24.0, ), decoration: InputDecoration( hintText: '${bpm * playbackSpeed}', hintStyle: TextStyle(color: blueGreen, fontSize: 24), alignLabelWithHint: true, filled: true, fillColor: Colors.transparent, border: InputBorder.none, enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, contentPadding: EdgeInsets.zero), cursorColor: Colors.white, onSubmitted: (String value) { _controller.clear(); try { int newBPM = int.parse(value); playbackSpeed = double.parse((newBPM / bpm).toStringAsFixed(2)); player.pause(); player.setPlaybackRate(playbackSpeed); setState(() {}); } catch (e) { ErrorPopup.show( context, "The BPM you entered is invalid."); } }, ), ), ], ), ], ), ], ), ), drawer: MyDrawer(index: 3), ); } String formatTime(Duration duration) { // Format single integers to 2 digits long (i.e. 02) String formatDigits(int n) => n.toString().padLeft(2, '0'); final hours = formatDigits(duration.inHours); final minutes = formatDigits(duration.inMinutes); final seconds = formatDigits( duration.inSeconds - 60 * (duration.inSeconds / 60).floor()); List<String> time = []; if (duration.inHours > 0) { time.add(hours); } time.add(minutes); time.add(seconds); return time.join(':'); } }
排查与修复方案
1. 等待seek异步操作完成再执行后续逻辑
player.seek()是异步方法,直接同步调用可能导致操作未完成就执行播放,修改滑块的onChanged逻辑:
onChanged: (value) async { currentTime = Duration(seconds: value.toInt()); setState(() { isPlaying = false; }); await player.pause(); await player.seek(currentTime); setState(() {}); }
2. 播放前校验并同步播放器位置
在播放按钮点击事件中,先获取播放器当前位置,和本地currentTime不一致时重新seek:
onPressed: () async { if (isPlaying) { await player.pause(); setState(() { isPlaying = false; }); } else { final currentPosition = await player.getCurrentPosition(); if (currentPosition != currentTime) { await player.seek(currentTime); } await player.resume(); setState(() { isPlaying = true; }); print(formatTime(currentTime)); } }
3. 初始化时等待音频加载完成
player.setUrl()是异步操作,直接设置固定songLength可能导致音频未加载完成时seek失效,修改初始化逻辑:
@override void initState() { super.initState(); initPlayer(); } void initPlayer() async { player.setReleaseMode(ReleaseMode.LOOP); // 等待音频加载完成 await player.setUrl(assetPath, isLocal: true); // 获取真实歌曲时长 final duration = await player.getDuration(); if (duration != null) { setState(() { songLength = duration; }); } player.onPlayerStateChanged.listen((state) { setState(() { isPlaying = state == PlayerState.PLAYING; }); }); player.onDurationChanged.listen((newSongLength) { setState(() { songLength = newSongLength; }); }); player.onAudioPositionChanged.listen((newCurrentTime) { setState(() { currentTime = newCurrentTime; }); }); }
4. 移除位置监听中的播放状态判断
原来的逻辑仅在播放时更新currentTime,导致暂停seek后本地变量与播放器实际位置不同步,播放时播放器返回初始位置0:
player.onAudioPositionChanged.listen((newCurrentTime) { setState(() { currentTime = newCurrentTime; }); });
内容的提问来源于stack exchange,提问作者babbid
相关产品推荐
相关产品推荐

