在这里插入图片描述

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

🎯 欢迎来到 Flutter for OpenHarmony 社区!本文将深入讲解 Flutter 中 flutter_sound 插件的音频播放功能,带你全面掌握在应用中播放音频的完整流程。


🎯 前言:为什么需要音频播放?

在移动应用开发中,音频播放功能是许多应用的核心特性:

场景一:音乐播放器需要播放音频文件
场景二:教育应用需要播放课程音频
场景三:有声读物应用需要播放音频内容
场景四:游戏应用需要播放背景音乐和音效

flutter_sound 是 Flutter 中功能强大的音频插件!它提供了完整的音频播放功能,支持多种音频格式,提供音频流处理能力,在 OpenHarmony 平台上基于鸿蒙原生音频服务实现。

🚀 核心能力一览

功能特性 详细说明 OpenHarmony 支持
音频播放 播放本地和网络音频
多种格式支持 支持 AAC、MP3、WAV 等格式
播放控制 暂停、恢复、停止、跳转播放
音量控制 调节播放音量
播放速度 调节播放速度
进度监听 监听播放进度

支持的功能

功能 说明 OpenHarmony 支持
openPlayer 打开播放器
closePlayer 关闭播放器
startPlayer 开始播放音频
stopPlayer 停止播放音频
pausePlayer 暂停播放
resumePlayer 恢复播放
seekToPlayer 跳转到指定位置
setVolume 设置音量
setSpeed 设置播放速度

⚙️ 环境准备:三步走

第一步:添加依赖

📄 pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  
  # 添加 flutter_sound 依赖(OpenHarmony 适配版本)
  flutter_sound:
    git:
      url: https://atomgit.com/openharmony-sig/flutter_sound.git
      path: flutter_sound

执行命令:

flutter pub get

第二步:准备音频文件

将音频文件放置在项目的 assets 目录中,并在 pubspec.yaml 中声明:

flutter:
  assets:
    - assets/audio/sample.mp3

📸 场景一:简单音频播放

📝 完整代码

import 'package:flutter/material.dart';
import 'package:flutter_sound/flutter_sound.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '简单音频播放',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2196F3)),
        useMaterial3: true,
      ),
      home: const SimplePlayerPage(),
    );
  }
}

class SimplePlayerPage extends StatefulWidget {
  const SimplePlayerPage({super.key});

  
  State<SimplePlayerPage> createState() => _SimplePlayerPageState();
}

class _SimplePlayerPageState extends State<SimplePlayerPage> {
  FlutterSoundPlayer? _player = FlutterSoundPlayer();
  bool _playerIsInited = false;
  String _audioPath = 'https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3';

  
  void initState() {
    super.initState();
    _initPlayer();
  }

  
  void dispose() {
    _player?.closePlayer();
    _player = null;
    super.dispose();
  }

  Future<void> _initPlayer() async {
    await _player!.openPlayer();
    setState(() {
      _playerIsInited = true;
    });
  }

  Future<void> _play() async {
    await _player!.startPlayer(
      fromURI: _audioPath,
      whenFinished: () {
        setState(() {});
      },
    );
    setState(() {});
  }

  Future<void> _stop() async {
    await _player!.stopPlayer();
    setState(() {});
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('简单音频播放'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.music_note, size: 100, color: Colors.blue),
            const SizedBox(height: 32),
            Text(
              _player!.isPlaying ? '播放中...' : '已停止',
              style: const TextStyle(fontSize: 24),
            ),
            const SizedBox(height: 32),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton.icon(
                  onPressed: _playerIsInited && _player!.isStopped ? _play : null,
                  icon: const Icon(Icons.play_arrow),
                  label: const Text('播放'),
                ),
                const SizedBox(width: 16),
                ElevatedButton.icon(
                  onPressed: _playerIsInited && _player!.isPlaying ? _stop : null,
                  icon: const Icon(Icons.stop),
                  label: const Text('停止'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

🎯 核心要点

  1. 初始化播放器:使用 openPlayer() 初始化
  2. 播放音频:使用 startPlayer() 播放
  3. 停止播放:使用 stopPlayer() 停止
  4. 状态管理:通过 isPlayingisStopped 判断状态

🎨 场景二:播放控制(暂停/恢复/跳转)

在这里插入图片描述

📝 完整代码

import 'package:flutter/material.dart';
import 'package:flutter_sound/flutter_sound.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '播放控制',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2196F3)),
        useMaterial3: true,
      ),
      home: const PlayerControlPage(),
    );
  }
}

class PlayerControlPage extends StatefulWidget {
  const PlayerControlPage({super.key});

  
  State<PlayerControlPage> createState() => _PlayerControlPageState();
}

class _PlayerControlPageState extends State<PlayerControlPage> {
  FlutterSoundPlayer? _player = FlutterSoundPlayer();
  bool _playerIsInited = false;
  Duration _duration = Duration.zero;
  Duration _position = Duration.zero;
  String _audioPath = 'https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3';

  
  void initState() {
    super.initState();
    _initPlayer();
  }

  
  void dispose() {
    _player?.closePlayer();
    _player = null;
    super.dispose();
  }

  Future<void> _initPlayer() async {
    await _player!.openPlayer();
    
    // 设置进度回调
    _player!.setSubscriptionDuration(const Duration(milliseconds: 100));
    
    setState(() {
      _playerIsInited = true;
    });
  }

  Future<void> _play() async {
    await _player!.startPlayer(
      fromURI: _audioPath,
      whenFinished: () {
        setState(() {
          _position = Duration.zero;
        });
      },
    );
    
    // 监听播放进度
    _player!.onProgress!.listen((event) {
      setState(() {
        _position = event.position;
        _duration = event.duration;
      });
    });
    
    setState(() {});
  }

  Future<void> _pause() async {
    await _player!.pausePlayer();
    setState(() {});
  }

  Future<void> _resume() async {
    await _player!.resumePlayer();
    setState(() {});
  }

  Future<void> _stop() async {
    await _player!.stopPlayer();
    setState(() {
      _position = Duration.zero;
    });
  }

  Future<void> _seekTo(Duration position) async {
    await _player!.seekToPlayer(position);
  }

  String _formatDuration(Duration duration) {
    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';
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('播放控制'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.music_note, size: 100, color: Colors.blue),
            const SizedBox(height: 32),
            
            // 进度条
            Column(
              children: [
                Slider(
                  value: _position.inMilliseconds.toDouble(),
                  max: _duration.inMilliseconds.toDouble() > 0
                      ? _duration.inMilliseconds.toDouble()
                      : 1.0,
                  onChanged: _player!.isPlaying
                      ? (value) {
                          _seekTo(Duration(milliseconds: value.toInt()));
                        }
                      : null,
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(_formatDuration(_position)),
                    Text(_formatDuration(_duration)),
                  ],
                ),
              ],
            ),
            
            const SizedBox(height: 32),
            
            // 播放状态
            Text(
              _player!.isPlaying
                  ? '播放中...'
                  : _player!.isPaused
                      ? '已暂停'
                      : '已停止',
              style: const TextStyle(fontSize: 24),
            ),
            
            const SizedBox(height: 32),
            
            // 控制按钮
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  iconSize: 48,
                  onPressed: _playerIsInited && _player!.isStopped ? _play : null,
                  icon: const Icon(Icons.play_arrow),
                ),
                IconButton(
                  iconSize: 48,
                  onPressed: _playerIsInited && _player!.isPlaying ? _pause : null,
                  icon: const Icon(Icons.pause),
                ),
                IconButton(
                  iconSize: 48,
                  onPressed: _playerIsInited && _player!.isPaused ? _resume : null,
                  icon: const Icon(Icons.play_arrow),
                ),
                IconButton(
                  iconSize: 48,
                  onPressed: _playerIsInited && !_player!.isStopped ? _stop : null,
                  icon: const Icon(Icons.stop),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

🎯 核心要点

  1. 暂停播放:使用 pausePlayer() 暂停
  2. 恢复播放:使用 resumePlayer() 恢复
  3. 跳转播放:使用 seekToPlayer() 跳转到指定位置
  4. 进度监听:通过 onProgress 监听播放进度
  5. 进度显示:使用 Slider 显示和控制播放进度

📊 场景三:音量和速度控制

📝 完整代码

import 'package:flutter/material.dart';
import 'package:flutter_sound/flutter_sound.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '音量和速度控制',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2196F3)),
        useMaterial3: true,
      ),
      home: const VolumeSpeedControlPage(),
    );
  }
}

class VolumeSpeedControlPage extends StatefulWidget {
  const VolumeSpeedControlPage({super.key});

  
  State<VolumeSpeedControlPage> createState() => _VolumeSpeedControlPageState();
}

class _VolumeSpeedControlPageState extends State<VolumeSpeedControlPage> {
  FlutterSoundPlayer? _player = FlutterSoundPlayer();
  bool _playerIsInited = false;
  double _volume = 1.0;
  double _speed = 1.0;
  String _audioPath = 'https://www.learningcontainer.com/wp-content/uploads/2020/02/Kalimba.mp3';

  
  void initState() {
    super.initState();
    _initPlayer();
  }

  
  void dispose() {
    _player?.closePlayer();
    _player = null;
    super.dispose();
  }

  Future<void> _initPlayer() async {
    await _player!.openPlayer();
    setState(() {
      _playerIsInited = true;
    });
  }

  Future<void> _play() async {
    await _player!.startPlayer(
      fromURI: _audioPath,
      whenFinished: () {
        setState(() {});
      },
    );
    
    // 设置初始音量和速度
    await _player!.setVolume(_volume);
    await _player!.setSpeed(_speed);
    
    setState(() {});
  }

  Future<void> _stop() async {
    await _player!.stopPlayer();
    setState(() {});
  }

  Future<void> _setVolume(double volume) async {
    await _player!.setVolume(volume);
    setState(() {
      _volume = volume;
    });
  }

  Future<void> _setSpeed(double speed) async {
    await _player!.setSpeed(speed);
    setState(() {
      _speed = speed;
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('音量和速度控制'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.music_note, size: 100, color: Colors.blue),
            const SizedBox(height: 32),
            
            // 播放状态
            Text(
              _player!.isPlaying ? '播放中...' : '已停止',
              style: const TextStyle(fontSize: 24),
            ),
            
            const SizedBox(height: 32),
            
            // 音量控制
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        const Icon(Icons.volume_up),
                        const SizedBox(width: 8),
                        Text(
                          '音量: ${(_volume * 100).toInt()}%',
                          style: const TextStyle(fontSize: 18),
                        ),
                      ],
                    ),
                    Slider(
                      value: _volume,
                      min: 0.0,
                      max: 1.0,
                      divisions: 10,
                      label: '${(_volume * 100).toInt()}%',
                      onChanged: _player!.isPlaying
                          ? (value) => _setVolume(value)
                          : null,
                    ),
                  ],
                ),
              ),
            ),
            
            const SizedBox(height: 16),
            
            // 速度控制
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        const Icon(Icons.speed),
                        const SizedBox(width: 8),
                        Text(
                          '速度: ${_speed.toStringAsFixed(1)}x',
                          style: const TextStyle(fontSize: 18),
                        ),
                      ],
                    ),
                    Slider(
                      value: _speed,
                      min: 0.5,
                      max: 2.0,
                      divisions: 15,
                      label: '${_speed.toStringAsFixed(1)}x',
                      onChanged: _player!.isPlaying
                          ? (value) => _setSpeed(value)
                          : null,
                    ),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        TextButton(
                          onPressed: _player!.isPlaying
                              ? () => _setSpeed(0.5)
                              : null,
                          child: const Text('0.5x'),
                        ),
                        TextButton(
                          onPressed: _player!.isPlaying
                              ? () => _setSpeed(1.0)
                              : null,
                          child: const Text('1.0x'),
                        ),
                        TextButton(
                          onPressed: _player!.isPlaying
                              ? () => _setSpeed(1.5)
                              : null,
                          child: const Text('1.5x'),
                        ),
                        TextButton(
                          onPressed: _player!.isPlaying
                              ? () => _setSpeed(2.0)
                              : null,
                          child: const Text('2.0x'),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            
            const SizedBox(height: 32),
            
            // 控制按钮
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton.icon(
                  onPressed: _playerIsInited && _player!.isStopped ? _play : null,
                  icon: const Icon(Icons.play_arrow),
                  label: const Text('播放'),
                ),
                const SizedBox(width: 16),
                ElevatedButton.icon(
                  onPressed: _playerIsInited && _player!.isPlaying ? _stop : null,
                  icon: const Icon(Icons.stop),
                  label: const Text('停止'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

🎯 核心要点

  1. 音量控制:使用 setVolume() 设置音量(0.0-1.0)
  2. 速度控制:使用 setSpeed() 设置播放速度(0.5-2.0)
  3. 实时调节:在播放过程中可以实时调节音量和速度
  4. 快捷按钮:提供常用速度的快捷按钮

📚 API 参考

FlutterSoundPlayer 主要方法

方法 说明 参数 返回值
openPlayer() 打开播放器 Future
closePlayer() 关闭播放器 Future
startPlayer() 开始播放音频 fromURI, codec, whenFinished Future
stopPlayer() 停止播放音频 Future
pausePlayer() 暂停播放 Future
resumePlayer() 恢复播放 Future
seekToPlayer() 跳转到指定位置 Duration Future
setVolume() 设置音量 double (0.0-1.0) Future
setSpeed() 设置播放速度 double (0.5-2.0) Future
setSubscriptionDuration() 设置进度回调间隔 Duration Future

播放器状态属性

属性 类型 说明
isPlaying bool 是否正在播放
isPaused bool 是否已暂停
isStopped bool 是否已停止
onProgress Stream 播放进度流

支持的音频格式

格式 扩展名 OpenHarmony 支持
AAC ADTS .aac
AAC MP4 .m4a
MP3 .mp3
WAV .wav
OGG Vorbis .ogg
OGG Opus .opus
FLAC .flac
PCM16 .pcm
PCM Float32 .pcm

🎓 最佳实践

1. 资源管理


void dispose() {
  // 确保关闭播放器
  _player?.closePlayer();
  _player = null;
  super.dispose();
}

2. 错误处理

Future<void> _play() async {
  try {
    await _player!.startPlayer(
      fromURI: _audioPath,
      whenFinished: () {
        setState(() {});
      },
    );
  } catch (e) {
    print('播放失败: $e');
    // 显示错误提示
  }
}

3. 状态检查

// 播放前检查状态
if (_player!.isStopped) {
  await _play();
}

// 暂停前检查状态
if (_player!.isPlaying) {
  await _pause();
}

4. 进度监听

// 设置合理的回调间隔
_player!.setSubscriptionDuration(const Duration(milliseconds: 100));

// 监听进度
_player!.onProgress!.listen((event) {
  setState(() {
    _position = event.position;
    _duration = event.duration;
  });
});

5. 网络音频处理

// 使用网络音频时添加加载提示
bool _isLoading = false;

Future<void> _playNetworkAudio() async {
  setState(() {
    _isLoading = true;
  });
  
  try {
    await _player!.startPlayer(
      fromURI: 'https://example.com/audio.mp3',
      whenFinished: () {
        setState(() {});
      },
    );
  } finally {
    setState(() {
      _isLoading = false;
    });
  }
}

🔧 故障排查

问题 1:无法播放音频

症状:调用 startPlayer() 后没有声音

可能原因

  1. 音频文件路径错误
  2. 音频格式不支持
  3. 设备音量为 0

解决方案

// 1. 检查文件路径
print('音频路径: $_audioPath');

// 2. 检查播放器状态
print('播放器状态: ${_player!.isPlaying}');

// 3. 设置音量
await _player!.setVolume(1.0);

问题 2:播放卡顿

症状:播放过程中出现卡顿

可能原因

  1. 网络音频加载慢
  2. 音频文件过大
  3. 设备性能不足

解决方案

// 1. 使用本地缓存
// 2. 降低音频质量
// 3. 优化 UI 更新频率
_player!.setSubscriptionDuration(const Duration(milliseconds: 500));

问题 3:进度不更新

症状:播放进度不更新

可能原因

  1. 未设置订阅间隔
  2. 未监听进度流

解决方案

// 设置订阅间隔
await _player!.setSubscriptionDuration(const Duration(milliseconds: 100));

// 监听进度
_player!.onProgress!.listen((event) {
  setState(() {
    _position = event.position;
    _duration = event.duration;
  });
});

问题 4:速度控制无效

症状:调用 setSpeed() 后速度没有变化

可能原因

  1. 音频格式不支持变速
  2. 在播放前设置速度

解决方案

// 在播放开始后设置速度
await _player!.startPlayer(fromURI: _audioPath);
await Future.delayed(const Duration(milliseconds: 100));
await _player!.setSpeed(1.5);


🔗 相关资源


Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐