Flutter (二十四) 音频

音频播放

flutter pub add audioplayers

  • 创建播放器对象AudioPlayer audioPlayer = AudioPlayer();
  • 开始播放audioPlayer.play(AssetSource(path));暂停播放audioPlayer.pause();
  • 监听播放状态audioPlayer.onPlayerStateChanged.listen((state) {})
  • 拖动进度条的时候,要禁用监听播放时间手动刷新进度条的操作。
php 复制代码
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

enum PlayMode { single, singleLoop, listLoop }

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'getx Demo',
      initialRoute: "/",
      routes: {"/": (context) => Home()},
    );
  }
}

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

  @override
  _HomeState createState() => _HomeState();
}

class _HomeState extends State<Home> {
  final List<Map<String, String>> musicList = [
    {"name": "当你", "artist": "王心凌", "path": "music/王心凌-当你.mp3"},
    {"name": "我的楼兰", "artist": "云朵", "path": "music/云朵-我的楼兰.mp3"},
    {"name": "我的心太乱", "artist": "周传雄", "path": "music/周传雄-我的心太乱.mp3"},
  ];

  final AudioPlayer audioPlayer = AudioPlayer();
  int? _currentIndex;
  bool _isPlaying = false;
  Duration _position = Duration.zero;
  Duration _duration = Duration.zero;
  double _volume = 1.0;
  bool _isDragging = false;
  PlayMode _playMode = PlayMode.listLoop;

  @override
  void initState() {
    super.initState();
    audioPlayer.onPlayerStateChanged.listen((state) {
      setState(() {
        switch (state) {
          case PlayerState.stopped:
            print("onPlayerStateChanged stopped");
            break;
          case PlayerState.playing:
            print("onPlayerStateChanged playing");
            break;
          case PlayerState.paused:
            print("onPlayerStateChanged paused");
            break;
          case PlayerState.completed:
            print("onPlayerStateChanged completed");
            break;
          case PlayerState.disposed:
            print("onPlayerStateChanged disposed");
            break;
        }

        _isPlaying = state == PlayerState.playing;
        if (state == PlayerState.completed) {
          switch (_playMode) {
            case PlayMode.singleLoop:
              if (_currentIndex != null) {
                audioPlayer.play(
                  AssetSource(musicList[_currentIndex!]["path"]!),
                );
              }
              break;
            case PlayMode.listLoop:
              if (_currentIndex != null) {
                int nextIndex = (_currentIndex! + 1) % musicList.length;
                _currentIndex = nextIndex;
                audioPlayer.play(AssetSource(musicList[nextIndex]["path"]!));
              }
              break;
            case PlayMode.single:
              _currentIndex = null;
              _isPlaying = false;
              _position = Duration.zero;
              break;
          }
        }
      });
    });
    audioPlayer.onPositionChanged.listen((pos) {
      if (!_isDragging) {
        setState(() {
          _position = pos;
        });
      }
    });
    audioPlayer.onDurationChanged.listen((dur) {
      setState(() {
        _duration = dur;
      });
    });
  }

  Future<void> playMusic(String path, int index) async {
    if (_currentIndex == index && _isPlaying) {
      await audioPlayer.pause();
    } else if (_currentIndex == index && !_isPlaying) {
      await audioPlayer.resume();
    } else {
      _currentIndex = index;
      await audioPlayer.play(AssetSource(path));
    }
  }

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

  Widget _buildMusicList() {
    return ListView(
      children: List.generate(musicList.length, (index) {
        final item = musicList[index];
        return ListTile(
          title: Text(item["name"]!),
          subtitle: Text(item["artist"]!),
          trailing: GestureDetector(
            child: Icon(
              _currentIndex == index && _isPlaying
                  ? Icons.pause
                  : Icons.play_arrow,
            ),
            onTap: () {
              playMusic(item["path"]!, index);
            },
          ),
        );
      }),
    );
  }

  // 这里创建一个当前音乐的进度条,并显示当前播放时间和总的播放时间。
  Widget _buildMusicStatus() {
    return Container(
      height: 140,
      color: Colors.red,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Row(
            children: [
              Text(
                _formatDuration(_position),
                style: const TextStyle(color: Colors.white),
              ),
              Expanded(
                child: Slider(
                  value:
                      (_duration.inSeconds > 0
                              ? _position.inSeconds.toDouble()
                              : 0.0)
                          .clamp(
                            0.0,
                            _duration.inSeconds.toDouble() > 0
                                ? _duration.inSeconds.toDouble()
                                : 1.0,
                          )
                          .toDouble(),
                  max: _duration.inSeconds.toDouble() > 0
                      ? _duration.inSeconds.toDouble()
                      : 1,
                  onChangeStart: (value) {
                    _isDragging = true;
                  },
                  onChanged: _duration.inSeconds > 0
                      ? (value) {
                          setState(() {
                            _position = Duration(seconds: value.toInt());
                          });
                        }
                      : null,
                  onChangeEnd: (value) {
                    audioPlayer.seek(Duration(seconds: value.toInt()));
                    _isDragging = false;
                  },
                ),
              ),
              Text(
                _formatDuration(_duration),
                style: const TextStyle(color: Colors.white),
              ),
            ],
          ),
          Row(
            children: [
              GestureDetector(
                onTap: () {
                  setState(() {
                    _playMode = PlayMode
                        .values[(_playMode.index + 1) % PlayMode.values.length];
                  });
                },
                child: Row(
                  children: [
                    Icon(_playModeIcon(), color: Colors.white, size: 20),
                    const SizedBox(width: 4),
                    Text(
                      _playModeLabel(),
                      style: const TextStyle(color: Colors.white),
                    ),
                  ],
                ),
              ),
              const SizedBox(width: 16),
              const Icon(Icons.volume_down, color: Colors.white, size: 20),
              Expanded(
                child: Slider(
                  value: _volume,
                  onChanged: (value) {
                    setState(() {
                      _volume = value;
                    });
                    audioPlayer.setVolume(value);
                  },
                ),
              ),
              const Icon(Icons.volume_up, color: Colors.white, size: 20),
            ],
          ),
        ],
      ),
    );
  }

  IconData _playModeIcon() {
    switch (_playMode) {
      case PlayMode.single:
        return Icons.looks_one;
      case PlayMode.singleLoop:
        return Icons.repeat_one;
      case PlayMode.listLoop:
        return Icons.repeat;
    }
  }

  String _playModeLabel() {
    switch (_playMode) {
      case PlayMode.single:
        return '单曲播放';
      case PlayMode.singleLoop:
        return '单曲循环';
      case PlayMode.listLoop:
        return '列表循环';
    }
  }

  String _formatDuration(Duration d) {
    String twoDigits(int n) => n.toString().padLeft(2, '0');
    final minutes = twoDigits(d.inMinutes.remainder(60));
    final seconds = twoDigits(d.inSeconds.remainder(60));
    return '$minutes:$seconds';
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('play music')),
      body: Container(
        color: Colors.green,
        width: double.infinity,
        height: double.infinity,
        child: Flex(
          direction: Axis.vertical,
          children: [
            Expanded(child: _buildMusicList()),
            _buildMusicStatus(),
          ],
        ),
      ),
    );
  }
}

使用GETX改造

music_controller.dart 复制代码
import 'package:audioplayers/audioplayers.dart';
import 'package:get/get.dart';

enum PlayMode { single, singleLoop, listLoop }

class MusicController extends GetxController {
  final musicList = <Map<String, String>>[
    {"name": "当你", "artist": "王心凌", "path": "music/王心凌-当你.mp3"},
    {"name": "我的楼兰", "artist": "云朵", "path": "music/云朵-我的楼兰.mp3"},
    {"name": "我的心太乱", "artist": "周传雄", "path": "music/周传雄-我的心太乱.mp3"},
  ];

  final audioPlayer = AudioPlayer();

  var currentIndex = Rxn<int>();
  var isPlaying = false.obs;
  var position = Duration.zero.obs;
  var duration = Duration.zero.obs;
  var volume = 1.0.obs;
  var playMode = PlayMode.listLoop.obs;

  bool _isDragging = false;

  @override
  void onInit() {
    super.onInit();
    audioPlayer.onPlayerStateChanged.listen((state) {
      isPlaying.value = state == PlayerState.playing;
      if (state == PlayerState.completed) {
        _handleComplete();
      }
    });
    audioPlayer.onPositionChanged.listen((pos) {
      if (!_isDragging) {
        position.value = pos;
      }
    });
    audioPlayer.onDurationChanged.listen((dur) {
      duration.value = dur;
    });
  }

  void _handleComplete() {
    switch (playMode.value) {
      case PlayMode.singleLoop:
        if (currentIndex.value != null) {
          audioPlayer.play(AssetSource(musicList[currentIndex.value!]["path"]!));
        }
        break;
      case PlayMode.listLoop:
        if (currentIndex.value != null) {
          int nextIndex = (currentIndex.value! + 1) % musicList.length;
          currentIndex.value = nextIndex;
          audioPlayer.play(AssetSource(musicList[nextIndex]["path"]!));
        }
        break;
      case PlayMode.single:
        currentIndex.value = null;
        isPlaying.value = false;
        position.value = Duration.zero;
        break;
    }
  }

  void playMusic(int index) {
    final path = musicList[index]["path"]!;
    if (currentIndex.value == index && isPlaying.value) {
      audioPlayer.pause();
    } else if (currentIndex.value == index && !isPlaying.value) {
      audioPlayer.resume();
    } else {
      currentIndex.value = index;
      audioPlayer.play(AssetSource(path));
    }
  }

  void seek(int seconds) {
    audioPlayer.seek(Duration(seconds: seconds));
  }

  void setDragging(bool dragging) {
    _isDragging = dragging;
  }

  void setVolume(double value) {
    volume.value = value;
    audioPlayer.setVolume(value);
  }

  void togglePlayMode() {
    playMode.value =
        PlayMode.values[(playMode.value.index + 1) % PlayMode.values.length];
  }

  @override
  void onClose() {
    audioPlayer.dispose();
    super.onClose();
  }
}

String formatDuration(Duration d) {
  String twoDigits(int n) => n.toString().padLeft(2, '0');
  final minutes = twoDigits(d.inMinutes.remainder(60));
  final seconds = twoDigits(d.inSeconds.remainder(60));
  return '$minutes:$seconds';
}
main.dart 复制代码
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getdemo/music_controller.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();


  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'getx Demo',
      initialRoute: "/",
      routes: {"/": (context) => Home()},
    );
  }
}

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

  final c = Get.put(MusicController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('play music')),
      body: Container(
        color: Colors.green,
        width: double.infinity,
        height: double.infinity,
        child: Flex(
          direction: Axis.vertical,
          children: [
            Expanded(child: _buildMusicList()),
            _buildMusicStatus(),
          ],
        ),
      ),
    );
  }

  Widget _buildMusicList() {
    return ListView(
      children: List.generate(c.musicList.length, (index) {
        final item = c.musicList[index];
        return ListTile(
          title: Text(item["name"]!),
          subtitle: Text(item["artist"]!),
          trailing: GestureDetector(
            child: Obx(() => Icon(
                  c.currentIndex.value == index && c.isPlaying.value
                      ? Icons.pause
                      : Icons.play_arrow,
                )),
            onTap: () => c.playMusic(index),
          ),
        );
      }),
    );
  }

  Widget _buildMusicStatus() {
    return Obx(() => Container(
          height: 140,
          color: Colors.red,
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Row(
                children: [
                  Text(formatDuration(c.position.value),
                      style: const TextStyle(color: Colors.white)),
                  Expanded(
                    child: Slider(
                      value: (c.duration.value.inSeconds > 0
                              ? c.position.value.inSeconds.toDouble()
                              : 0.0)
                          .clamp(
                            0.0,
                            c.duration.value.inSeconds.toDouble() > 0
                                ? c.duration.value.inSeconds.toDouble()
                                : 1.0,
                          )
                          .toDouble(),
                      max: c.duration.value.inSeconds.toDouble() > 0
                          ? c.duration.value.inSeconds.toDouble()
                          : 1,
                      onChangeStart: (value) {
                        c.setDragging(true);
                      },
                      onChanged: c.duration.value.inSeconds > 0
                          ? (value) {
                              c.position.value =
                                  Duration(seconds: value.toInt());
                            }
                          : null,
                      onChangeEnd: (value) {
                        c.seek(value.toInt());
                        c.setDragging(false);
                      },
                    ),
                  ),
                  Text(formatDuration(c.duration.value),
                      style: const TextStyle(color: Colors.white)),
                ],
              ),
              Row(
                children: [
                  GestureDetector(
                    onTap: c.togglePlayMode,
                    child: Row(
                      children: [
                        Icon(_playModeIcon(c.playMode.value),
                            color: Colors.white, size: 20),
                        const SizedBox(width: 4),
                        Text(_playModeLabel(c.playMode.value),
                            style: const TextStyle(color: Colors.white)),
                      ],
                    ),
                  ),
                  const SizedBox(width: 16),
                  const Icon(Icons.volume_down, color: Colors.white, size: 20),
                  Expanded(
                    child: Slider(
                      value: c.volume.value,
                      onChanged: (value) {
                        c.setVolume(value);
                      },
                    ),
                  ),
                  const Icon(Icons.volume_up, color: Colors.white, size: 20),
                ],
              ),
            ],
          ),
        ));
  }

  IconData _playModeIcon(PlayMode mode) {
    switch (mode) {
      case PlayMode.single:
        return Icons.looks_one;
      case PlayMode.singleLoop:
        return Icons.repeat_one;
      case PlayMode.listLoop:
        return Icons.repeat;
    }
  }

  String _playModeLabel(PlayMode mode) {
    switch (mode) {
      case PlayMode.single:
        return '单曲播放';
      case PlayMode.singleLoop:
        return '单曲循环';
      case PlayMode.listLoop:
        return '列表循环';
    }
  }
}
相关推荐
愚公搬代码1 小时前
【愚公系列】《Web应用安全》012-Behinder工具的使用
前端·安全
aixingpan1 小时前
aixingpan.cn API开发文档:api_docs_errors接口指南
前端·php
李剑一1 小时前
前端转AI要了解的技术,其他人不用看。前端架构基础之:让Js的计算运行在GPU上
前端·aigc·ai编程
IT_陈寒1 小时前
Python的列表拷贝坑得我原地打转
前端·人工智能·后端
恋猫de小郭2 小时前
Dart 3.13 的到底改了什么?为什么很重要?有什么坑?
android·前端·flutter
风月说与山鬼2 小时前
六、React事件
前端·javascript·react.js
岁岁种桃花儿2 小时前
Vue核心语法第十四篇:列表过滤
前端·javascript·vue.js
晓得迷路了2 小时前
栗子前端技术周刊第 143 期 - npm 攻击事件、Bun 1.4...
前端·javascript
YHHLAI2 小时前
实战 Vibe Coding:用 AI 从零搭一个 React 待办清单
前端·人工智能·react.js