You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 19:37:03