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

Flutter中errors_patch.dart的_throw方法异常排查求助

Flutter音频书籍应用章节切换异常原因分析与解决办法

异常原因

  1. 数组索引越界
    章节号从1开始计数,但widget.audioBookData["chapters"]是0-based数组。你直接用章节号+1作为数组索引,会跳过正确位置甚至超出数组长度触发异常。比如当前章节号为1(对应数组索引0),取下一章时你用索引2,直接访问了第3章的位置;如果当前是最后一章,章节号+1会超出数组最大索引,直接抛出越界错误。

  2. 未清理旧的Stream订阅
    每次调用setupPlayer都会重新监听positionStream,但旧订阅未取消,导致多个订阅同时运行。切换章节时旧订阅仍会触发逻辑,造成状态混乱甚至未处理异常。

  3. Player资源未重置
    切换章节时直接调用setUrl,未先重置Player释放旧音频资源,容易引发资源冲突导致内部异常。

  4. 空指针未处理
    直接强制解包player.duration!,若音频加载失败duration为null,会触发空指针异常。

解决步骤

  1. 修正数组索引逻辑
    章节号转数组索引时用章节号-1,取下一章时计算当前索引再加1,匹配数组的0-based规则。

  2. 管理Stream订阅
    定义StreamSubscription?变量保存订阅,每次调用setupPlayer前取消旧订阅,避免多订阅冲突。

  3. 切换章节时重置Player
    在setupPlayer开头调用player.reset(),释放旧资源后再加载新音频。

  4. 添加空安全与异常捕获
    对player.duration做空判断,在异步操作(Firebase下载、Player设置)中加入try-catch,避免未处理异常。

修复后的完整代码

import 'dart:convert';
import 'dart:io';

import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:path_provider/path_provider.dart';

class PlayAudioBookScreen extends StatefulWidget {
  final Map<String, dynamic> initialChapterData;
  final Map<String, dynamic> audioBookData;
  const PlayAudioBookScreen(this.initialChapterData, this.audioBookData, {super.key});

  @override
  State<PlayAudioBookScreen> createState() => PlayAudioBookScreenState();
}

class PlayAudioBookScreenState extends State<PlayAudioBookScreen> {
  AudioPlayer player = AudioPlayer();
  bool playing = true;
  double sliderPos = 0;
  String bookTitle = "";
  String chapterTitle = "";
  String author = "";
  String audioFileLocalPath = "";
  String audioFileFirebasePath = "";
  int audioFileDuration = 0; // 单位:毫秒
  Map<String, dynamic> currentChapterData = jsonDecode("{}");
  StreamSubscription? positionSubscription; // 保存播放位置监听订阅

  @override
  void initState() {
    bookTitle = widget.audioBookData["title"] ?? "";
    author = widget.audioBookData["author"] ?? "";
    currentChapterData = widget.initialChapterData;
    chapterTitle = currentChapterData["title"] ?? "";
    audioFileLocalPath = currentChapterData["localPath"] ?? "";
    audioFileFirebasePath = currentChapterData["firebasePath"] ?? "";
    setupPlayer();
    super.initState();
  }

  void setupPlayer() async {
    // 取消旧的播放位置监听
    await positionSubscription?.cancel();

    try {
      // 重置播放器释放旧资源
      await player.reset();

      Directory appDocDir = await getApplicationDocumentsDirectory();
      File audioFile = File("${appDocDir.path}/$audioFileLocalPath");
      if (!await audioFile.exists()) {
        await audioFile.create(recursive: true);
        print("开始下载音频文件");
        Reference audioFileFirebaseRef = FirebaseStorage.instance.ref(audioFileFirebasePath);
        await audioFileFirebaseRef.writeToFile(audioFile);
      }

      await player.setUrl(audioFile.path);
      // 等待音频时长加载完成
      await player.durationFuture;

      // 空判断避免空指针
      if (player.duration != null) {
        audioFileDuration = player.duration!.inMilliseconds;
      } else {
        print("音频文件加载失败,无法获取时长");
        return;
      }

      // 监听播放位置
      positionSubscription = player.positionStream.listen((position) {
        if (position.inMilliseconds >= audioFileDuration) {
          int currentChapterNum = int.tryParse(currentChapterData["chapterNumber"] ?? "0") ?? 0;
          int totalChapters = widget.audioBookData["chapters"]?.length ?? 0;
          // 修正索引逻辑:章节号从1开始,数组索引从0开始
          if (currentChapterNum < totalChapters) {
            print("准备切换到下一章");
            int currentIndex = currentChapterNum - 1;
            int nextIndex = currentIndex + 1;
            if (nextIndex < totalChapters) {
              setupNewChapter(widget.audioBookData["chapters"][nextIndex]);
            }
          } else {
            print("已播放到最后一章");
            player.pause();
            setState(() {
              sliderPos = audioFileDuration.toDouble();
              playing = false;
            });
          }
        } else {
          setState(() {
            sliderPos = position.inMilliseconds.toDouble();
          });
        }
      });

      await player.play();
      setState(() {
        playing = true;
      });
    } catch (e, stackTrace) {
      print("播放设置出错: $e");
      print(stackTrace);
      setState(() {
        playing = false;
      });
    }
  }

  void setupNewChapter(Map<String, dynamic> chapterData) {
    print("开始切换章节");
    setState(() {
      currentChapterData = chapterData;
      chapterTitle = currentChapterData["title"] ?? "";
      audioFileLocalPath = currentChapterData["localPath"] ?? "";
      audioFileFirebasePath = currentChapterData["firebasePath"] ?? "";
    });
    setupPlayer();
  }

  @override
  void dispose() {
    // 页面销毁时清理资源
    positionSubscription?.cancel();
    player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(children: [
        const SizedBox(height: 80),
        Text(bookTitle, style: const TextStyle(fontWeight: FontWeight.bold)),
        Text(chapterTitle, style: const TextStyle(fontWeight: FontWeight.bold)),
        Text("by $author"),
        const SizedBox(height: 20),
        playing
            ? IconButton(
                icon: const Icon(Icons.pause),
                onPressed: () async {
                  await player.pause();
                  setState(() {
                    playing = false;
                  });
                },
              )
            : IconButton(
                icon: const Icon(Icons.play_arrow),
                onPressed: () async {
                  await player.play();
                  setState(() {
                    playing = true;
                  });
                },
              ),
        Slider(
            min: 0,
            max: audioFileDuration.toDouble(),
            value: sliderPos.clamp(0, audioFileDuration.toDouble()), // 防止滑块超出范围
            onChanged: (value) async {
              await player.seek(Duration(milliseconds: value.toInt()));
            }),
      ]),
    );
  }
}

内容的提问来源于stack exchange,提问作者Ameen Izhac

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 00:00:08