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

Flutter音频播放器:页面加载时无法显示音乐总时长求助

解决Flutter audioplayers播放器页面加载后直接显示总时长的问题

你当前的播放器在页面初始化后总时长显示为0:0,只有点击播放按钮后才会正常显示。核心原因是initPlayer方法中获取到音乐总时长后没有触发UI刷新,同时时长格式化逻辑也存在细节缺陷(比如秒数小于10时不会补零)。以下是具体解决方案:


1. 修复初始化时的状态更新

在initPlayer方法中获取到duration后,调用setState()让UI立刻刷新:

void initPlayer() async {
  await player.setSource(AssetSource("forest.mp3"));
  duration = await player.getDuration();
  setState(() {}); // 获取总时长后立即触发UI更新
  player.onPositionChanged.listen((Duration d) {
    setState(() {
      value = d.inSeconds.toDouble();
    });
  });
}

2. 添加时长格式化工具函数

创建统一的格式化函数,确保分钟和秒数都显示为两位数(比如将1:3转为1:03):

String formatDuration(Duration? duration) {
  if (duration == null) return "0:00";
  String twoDigits(int n) => n.toString().padLeft(2, '0');
  final minutes = twoDigits(duration.inMinutes.remainder(60));
  final seconds = twoDigits(duration.inSeconds.remainder(60));
  return "$minutes:$seconds";
}

3. 优化UI中的时长显示

使用工具函数替换硬编码的格式化逻辑,同时处理duration为空的情况:

// 已播放时长
Text(
  formatDuration(Duration(seconds: value.toInt())),
  style: const TextStyle(color: Colors.white),
),
// 总时长
Text(
  formatDuration(duration),
  style: const TextStyle(color: Colors.white),
),

4. 移除播放按钮中冗余的duration获取

initPlayer已经在初始化时获取了总时长,播放按钮中无需重复执行:

onTap: () async {
  if (isPlaying) {
    await player.pause();
    setState(() => isPlaying = false);
  } else {
    await player.resume();
    setState(() => isPlaying = true);
  }
},

完整修改后的代码

import 'package:flutter/material.dart';
import 'package:audioplayers/audioplayers.dart';
import 'dart:ui';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String img_cover_url = "https://i.pinimg.com/736x/a7/a9/cb/a7a9cbcefc58f5b677d8c480cf4ddc5d.jpg";

  bool isPlaying = false;
  double value = 0;
  final player = AudioPlayer();
  Duration? duration; // 初始化为null,避免默认0值干扰
  Duration? position;

  // 统一格式化时长
  String formatDuration(Duration? duration) {
    if (duration == null) return "0:00";
    String twoDigits(int n) => n.toString().padLeft(2, '0');
    final minutes = twoDigits(duration.inMinutes.remainder(60));
    final seconds = twoDigits(duration.inSeconds.remainder(60));
    return "$minutes:$seconds";
  }

  void initPlayer() async {
    await player.setSource(AssetSource("forest.mp3"));
    duration = await player.getDuration();
    setState(() {}); // 刷新UI显示总时长
    player.onPositionChanged.listen((Duration d) {
      setState(() {
        value = d.inSeconds.toDouble();
      });
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          Container(
            constraints: const BoxConstraints.expand(),
            decoration: const BoxDecoration(
              image: DecorationImage(
                image: AssetImage("assets/cover.jpg"),
                fit: BoxFit.cover,
              ),
            ),
            child: BackdropFilter(
              filter: ImageFilter.blur(sigmaX: 28, sigmaY: 28),
              child: Container(color: Colors.black.withOpacity(0.6)),
            ),
          ),
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              ClipRRect(
                borderRadius: BorderRadius.circular(30.0),
                child: Image.asset("assets/cover.jpg", width: 250.0),
              ),
              const SizedBox(height: 10.0),
              const Text(
                "Summer",
                style: TextStyle(color: Colors.white, fontSize: 36, letterSpacing: 6),
              ),
              const SizedBox(height: 50.0),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Text(
                    formatDuration(Duration(seconds: value.toInt())),
                    style: const TextStyle(color: Colors.white),
                  ),
                  const SizedBox(width: 10),
                  Expanded(
                    child: Slider.adaptive(
                      onChanged: (v) {
                        setState(() {
                          value = v;
                        });
                      },
                      min: 0.0,
                      max: duration?.inSeconds.toDouble() ?? 0.0,
                      value: value.clamp(0.0, duration?.inSeconds.toDouble() ?? 0.0),
                      onChangeEnd: (newValue) async {
                        setState(() {
                          value = newValue;
                        });
                        await player.seek(Duration(seconds: newValue.toInt()));
                        if (isPlaying) await player.resume();
                      },
                      activeColor: Colors.white,
                    ),
                  ),
                  const SizedBox(width: 10),
                  Text(
                    formatDuration(duration),
                    style: const TextStyle(color: Colors.white),
                  ),
                ],
              ),
              const SizedBox(height: 60.0),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(60.0),
                      color: Colors.black87,
                      border: Border.all(color: Colors.white38),
                    ),
                    width: 50.0,
                    height: 50.0,
                    child: InkWell(
                      onTapDown: (details) => player.setPlaybackRate(0.5),
                      onTapUp: (details) => player.setPlaybackRate(1),
                      child: const Center(child: Icon(Icons.fast_rewind_rounded, color: Colors.white)),
                    ),
                  ),
                  const SizedBox(width: 60.0),
                  Container(
                    width: 50.0,
                    height: 50.0,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(60.0),
                      color: Colors.black87,
                      border: Border.all(color: Colors.pink),
                    ),
                    child: InkWell(
                      onTap: () async {
                        if (isPlaying) {
                          await player.pause();
                          setState(() => isPlaying = false);
                        } else {
                          await player.resume();
                          setState(() => isPlaying = true);
                        }
                      },
                      child: Icon(isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.white),
                    ),
                  ),
                  const SizedBox(width: 60.0),
                  Container(
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(60.0),
                      color: Colors.black87,
                      border: Border.all(color: Colors.white38),
                    ),
                    width: 50.0,
                    height: 50.0,
                    child: InkWell(
                      onTapDown: (details) => player.setPlaybackRate(2),
                      onTapUp: (details) => player.setPlaybackRate(1),
                      child: const Center(child: Icon(Icons.fast_forward_rounded, color: Colors.white)),
                    ),
                  ),
                ],
              )
            ],
          ),
        ],
      ),
    );
  }
}

额外优化说明

  • 将duration初始值改为null,配合格式化函数避免初始显示错误
  • 给Slider添加Expanded包裹,解决布局溢出问题
  • 限制Slider的value范围,防止超出总时长
  • 简化播放按钮逻辑,移除冗余的重复操作

内容的提问来源于stack exchange,提问作者Bella Lee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 15:35:26