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

Flutter中如何避免build重复调用导致AudioPlayer音频重复播放

解决Flutter中build多次调用导致音频重复播放的问题

问题描述

我正在开发一款按顺序播放图片与音频列表的App,音频播放完成后切换至下一组图片和音频,通过setState()更新图片。每次调用setState()后,build()方法会被调用两次,导致音频重复播放。请问如何让播放逻辑在build时仅执行一次?

原代码:

class PlayingScreen extends StatefulWidget {
  final PostModel postModel;

  PlayingScreen({
    Key? key,
    required this.postModel,
  }) : super(key: key);

  @override
  State<PlayingScreen> createState() => _PlayingScreenState();
}

class _PlayingScreenState extends State<PlayingScreen> {
  final _audioPlayer = AudioPlayer(); 
  List<String> _audios = []; // audio file path list
  int _playIndex = 0;

  @override
  void initState() {
    _getAudios();
    super.initState();
  }

  @override
  void dispose() {
    _audioPlayer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    _play(_playIndex);

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.postModel.title),
      ),
      body: InkWell(
        onTap: () {
          Get.back();
        },

       // image widget
        ),
      ),
    );
  }

  Future _play(int index) async {
    await _audioPlayer.play(DeviceFileSource(_audios[index]));
    logger.d("current index: $_playIndex / ${_audios.length - 1}");
    //complete event
    _audioPlayer.onPlayerComplete.listen((event) {
      logger.d("complet, next");
      setState(() {
        if (_playIndex >= _imageSets.length - 1) {
          _playIndex = 0;
          logger.d("return first index");
        } else {
          _playIndex++;
        }
      });
    });
  }
}

核心问题分析

build()是Flutter用于构建UI的回调,会在setState()、屏幕旋转、父组件重建等多种场景下触发,绝对不能在build中执行播放、网络请求这类有副作用的逻辑,否则必然导致重复执行甚至异常。

修正方案

  1. 移除build()中的_play()调用,将播放逻辑移到生命周期回调或音频完成事件中
  2. 在initState()中启动首次播放,并仅监听一次音频完成事件,避免重复订阅
  3. 音频完成后直接触发下一次播放,无需依赖build()执行播放逻辑

修正后的代码:

class PlayingScreen extends StatefulWidget {
  final PostModel postModel;

  PlayingScreen({
    Key? key,
    required this.postModel,
  }) : super(key: key);

  @override
  State<PlayingScreen> createState() => _PlayingScreenState();
}

class _PlayingScreenState extends State<PlayingScreen> {
  final _audioPlayer = AudioPlayer(); 
  List<String> _audios = []; // 音频文件路径列表
  int _playIndex = 0;
  StreamSubscription? _completeSubscription; // 保存订阅对象,用于取消监听

  @override
  void initState() {
    super.initState();
    // 音频列表加载完成后启动首次播放
    _getAudios().then((_) {
      _playCurrent();
      // 仅订阅一次音频完成事件,避免重复触发
      _completeSubscription = _audioPlayer.onPlayerComplete.listen((_) {
        _playNext();
      });
    });
  }

  @override
  void dispose() {
    _completeSubscription?.cancel(); // 取消订阅,防止内存泄漏
    _audioPlayer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.postModel.title),
      ),
      body: InkWell(
        onTap: () {
          Get.back();
        },
        // 展示当前索引对应的图片
        child: // 替换为你的图片Widget,例如 Image.asset(_imageSets[_playIndex])
      ),
    );
  }

  // 异步加载音频列表
  Future<void> _getAudios() async {
    // 替换为你的实际加载逻辑,比如从postModel中获取音频路径
    _audios = await // 音频列表加载逻辑
  }

  // 播放当前索引的音频
  Future<void> _playCurrent() async {
    if (_audios.isEmpty) return;
    await _audioPlayer.play(DeviceFileSource(_audios[_playIndex]));
    logger.d("current index: $_playIndex / ${_audios.length - 1}");
  }

  // 切换到下一个音频并播放
  void _playNext() {
    setState(() {
      if (_playIndex >= _audios.length - 1) {
        _playIndex = 0;
        logger.d("return first index");
      } else {
        _playIndex++;
      }
    });
    // 状态更新后播放新音频
    _playCurrent();
  }
}

关键说明

  • 彻底移除build()中的播放逻辑,从根源避免build多次调用带来的重复播放
  • 使用StreamSubscription管理音频完成事件的订阅,在dispose()中取消订阅,防止内存泄漏
  • 首次播放放在_getAudios()的回调中,确保音频列表加载完成后再执行播放
  • 音频完成后直接调用_playNext(),通过setState()更新索引后播放新音频,逻辑更清晰可控

内容的提问来源于stack exchange,提问作者Kevin Yang

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 21:50:28