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

Flutter iOS端onDurationChanged未触发问题求助

iOS端audioplayers网络音频onDurationChanged不触发的解决方案

问题概述

使用audioplayers: 1.1.0播放网络音频时,Android端能正常通过onDurationChanged获取时长,但iOS端该回调未被触发;本地音频在iOS端功能正常。

解决方案

1. 先设置音频源再播放(优先尝试)

iOS端直接调用play可能跳过元数据加载步骤,导致时长回调未触发。改为先通过setSource加载音频源,等待元数据就绪后再播放:

修改播放按钮的点击逻辑:

onPressed: () async {
  if (isPlaying) {
    player.pause();
  } else {
    final source = UrlSource('https://www.kozco.com/tech/LRMonoPhase4.mp3');
    // 先设置源,触发元数据加载
    await player.setSource(source);
    // 再播放
    await player.play(source);
  }
},

2. 调整iOS音频会话配置

当前使用的AVAudioSessionCategory.ambient类别可能限制元数据获取,改为playback类别:

final AudioContext audioContext = AudioContext(
  iOS: AudioContextIOS(
    defaultToSpeaker: true,
    category: AVAudioSessionCategory.playback, // 修改此处
    options: [
      AVAudioSessionOptions.defaultToSpeaker,
      AVAudioSessionOptions.mixWithOthers,
    ],
  ),
  // Android配置保持不变
  android: AudioContextAndroid(
    isSpeakerphoneOn: true,
    stayAwake: true,
    contentType: AndroidContentType.sonification,
    usageType: AndroidUsageType.assistanceSonification,
    audioFocus: AndroidAudioFocus.none,
  ),
);

3. 升级audioplayers版本

1.1.0版本存在较多iOS端网络音频相关bug,升级至最新稳定版可解决大部分兼容性问题:
修改pubspec.yaml:

dependencies:
  audioplayers: ^3.0.1 # 替换为最新稳定版本

执行flutter pub get更新依赖后,重新测试。

4. 手动获取音频时长(备选方案)

若以上方法无效,可通过HTTP请求获取音频文件的Content-Length,结合音频码率计算时长(需服务端支持返回正确的Content-Length),或使用第三方库解析远程音频的元数据。

修改后的完整代码示例

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Audio Player'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final player = AudioPlayer();
  bool isPlaying = false;
  Duration duration = Duration.zero;
  Duration position = Duration.zero;

  String formatTime(int seconds) {
    return '${(Duration(seconds: seconds))}'.split('.')[0].padLeft(8, '0');
  }

  final AudioContext audioContext = AudioContext(
    iOS: AudioContextIOS(
      defaultToSpeaker: true,
      category: AVAudioSessionCategory.playback,
      options: [
        AVAudioSessionOptions.defaultToSpeaker,
        AVAudioSessionOptions.mixWithOthers,
      ],
    ),
    android: AudioContextAndroid(
      isSpeakerphoneOn: true,
      stayAwake: true,
      contentType: AndroidContentType.sonification,
      usageType: AndroidUsageType.assistanceSonification,
      audioFocus: AndroidAudioFocus.none,
    ),
  );

  @override
  void initState() {
    super.initState();
    AudioPlayer.global.setGlobalAudioContext(audioContext);

    player.onPlayerStateChanged.listen((state) {
      setState(() {
        isPlaying = state == PlayerState.playing;
      });
    });

    player.onDurationChanged.listen((newDuration) {
      setState(() {
        duration = newDuration;
      });
    });

    player.onPositionChanged.listen((newPosition) {
      setState(() {
        position = newPosition;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Slider(
              min: 0,
              max: duration.inSeconds.toDouble(),
              value: position.inSeconds.toDouble(),
              onChanged: (value) {
                final position = Duration(seconds: value.toInt());
                player.seek(position);
                player.resume();
              },
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CircleAvatar(
                  radius: 25,
                  child: IconButton(
                    icon: Icon(
                      isPlaying ? Icons.pause : Icons.play_arrow,
                    ),
                    onPressed: () async {
                      if (isPlaying) {
                        player.pause();
                      } else {
                        final source = UrlSource('https://www.kozco.com/tech/LRMonoPhase4.mp3');
                        await player.setSource(source);
                        await player.play(source);
                      }
                    },
                  ),
                ),
                SizedBox(
                  width: 20,
                ),
                CircleAvatar(
                  radius: 25,
                  child: IconButton(
                    icon: const Icon(Icons.stop),
                    onPressed: () {
                      player.stop();
                    },
                  ),
                ),
              ],
            ),
            Container(
              padding: const EdgeInsets.all(20),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(formatTime(position.inSeconds)),
                  Text(formatTime((duration - position).inSeconds)),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者Anandh Krishnan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 08:40:22