Flutter音频管理插件audio_manager的使用

发布于 1周前 作者 yuanlaile 来自 Flutter

Flutter音频管理插件audio_manager的使用

audio_manager 是一个用于Flutter应用程序中实现音乐播放功能的插件,它支持本地资源、文件目录和网络资源的播放,并且可以处理通知。该插件基于iOS上的AVPlayer和Android上的MediaPlayer开发。

iOS配置

info.plist文件中添加以下权限:

<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

⚠️ 注意:某些方法在模拟器上可能无效,请使用真实设备进行测试。

Android配置

Android9.0 (API 28)开始,默认情况下禁用HTTP明文请求。如果需要允许请求,则需在AndroidManifest.xml中添加android:usesCleartextTraffic="true"

<application
    ...
    android:usesCleartextTraffic="true"
    ...>

⚠️ 注意事项:

  • Android最低支持版本为23 (app/build.gradle -> minSdkVersion: 23)
  • Android最低支持Gradle版本为5.4.1 (gradle-wrapper.properties -> gradle-5.4.1-all.zip)

快速开始

audio_manager 插件采用单例模式开发,只需要通过 AudioManager.instance 获取实例即可快速开始使用。

示例代码

下面是一个完整的示例demo,展示了如何使用audio_manager插件来创建一个简单的音频播放器应用。

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

import 'package:flutter/services.dart';
import 'package:audio_manager/audio_manager.dart';
import 'package:path_provider/path_provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  bool isPlaying = false;
  Duration _duration;
  Duration _position;
  double _slider;
  double _sliderVolume;
  String _error;
  num curIndex = 0;
  PlayMode playMode = AudioManager.instance.playMode;

  final list = [
    {
      "title": "Assets",
      "desc": "assets playback",
      "url": "assets/xv.mp3",
      "coverUrl": "assets/ic_launcher.png"
    },
    {
      "title": "network",
      "desc": "network resouce playback",
      "url": "https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.m4a",
      "coverUrl": "https://homepages.cae.wisc.edu/~ece533/images/airplane.png"
    }
  ];

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

    initPlatformState();
    setupAudio();
    loadFile();
  }

  @override
  void dispose() {
    // Release all resources
    AudioManager.instance.release();
    super.dispose();
  }

  void setupAudio() {
    List<AudioInfo> _list = [];
    list.forEach((item) => _list.add(AudioInfo(item["url"],
        title: item["title"], desc: item["desc"], coverUrl: item["coverUrl"])));

    AudioManager.instance.audioList = _list;
    AudioManager.instance.intercepter = true;
    AudioManager.instance.play(auto: false);

    AudioManager.instance.onEvents((events, args) {
      print("$events, $args");
      switch (events) {
        case AudioManagerEvents.start:
          print(
              "start load data callback, curIndex is ${AudioManager.instance.curIndex}");
          _position = AudioManager.instance.position;
          _duration = AudioManager.instance.duration;
          _slider = 0;
          setState(() {});
          AudioManager.instance.updateLrc("audio resource loading....");
          break;
        case AudioManagerEvents.ready:
          print("ready to play");
          _error = null;
          _sliderVolume = AudioManager.instance.volume;
          _position = AudioManager.instance.position;
          _duration = AudioManager.instance.duration;
          setState(() {});
          break;
        case AudioManagerEvents.seekComplete:
          _position = AudioManager.instance.position;
          _slider = _position.inMilliseconds / _duration.inMilliseconds;
          setState(() {});
          print("seek event is completed. position is [$args]/ms");
          break;
        case AudioManagerEvents.buffering:
          print("buffering $args");
          break;
        case AudioManagerEvents.playstatus:
          isPlaying = AudioManager.instance.isPlaying;
          setState(() {});
          break;
        case AudioManagerEvents.timeupdate:
          _position = AudioManager.instance.position;
          _slider = _position.inMilliseconds / _duration.inMilliseconds;
          setState(() {});
          AudioManager.instance.updateLrc(args["position"].toString());
          break;
        case AudioManagerEvents.error:
          _error = args;
          setState(() {});
          break;
        case AudioManagerEvents.ended:
          AudioManager.instance.next();
          break;
        case AudioManagerEvents.volumeChange:
          _sliderVolume = AudioManager.instance.volume;
          setState(() {});
          break;
        default:
          break;
      }
    });
  }

  void loadFile() async {
    // Read bundle file to local path
    final audioFile = await rootBundle.load("assets/aLIEz.m4a");
    final audio = audioFile.buffer.asUint8List();

    final appDocDir = await getApplicationDocumentsDirectory();
    print(appDocDir);

    final file = File("${appDocDir.path}/aLIEz.m4a");
    file.writeAsBytesSync(audio);

    AudioInfo info = AudioInfo("file://${file.path}",
        title: "file", desc: "local file", coverUrl: "assets/aLIEz.jpg");

    list.add(info.toJson());
    AudioManager.instance.audioList.add(info);
    setState(() {});
  }

  Future<void> initPlatformState() async {
    String platformVersion;
    try {
      platformVersion = await AudioManager.instance.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin audio player'),
        ),
        body: Center(
          child: Column(
            children: <Widget>[
              Text('Running on: $_platformVersion\n'),
              Padding(
                padding: EdgeInsets.symmetric(horizontal: 16),
                child: volumeFrame(),
              ),
              Expanded(
                child: ListView.separated(
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text(list[index]["title"],
                            style: TextStyle(fontSize: 18)),
                        subtitle: Text(list[index]["desc"]),
                        onTap: () => AudioManager.instance.play(index: index),
                      );
                    },
                    separatorBuilder: (BuildContext context, int index) =>
                        Divider(),
                    itemCount: list.length),
              ),
              Center(
                  child: Text(_error != null
                      ? _error
                      : "${AudioManager.instance.info.title} lrc text: $_position")),
              bottomPanel()
            ],
          ),
        ),
      ),
    );
  }

  Widget bottomPanel() {
    return Column(children: <Widget>[
      Padding(
        padding: EdgeInsets.symmetric(horizontal: 16),
        child: songProgress(context),
      ),
      Container(
        padding: EdgeInsets.symmetric(vertical: 16),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            IconButton(
                icon: getPlayModeIcon(playMode),
                onPressed: () {
                  playMode = AudioManager.instance.nextMode();
                  setState(() {});
                }),
            IconButton(
                iconSize: 36,
                icon: Icon(
                  Icons.skip_previous,
                  color: Colors.black,
                ),
                onPressed: () => AudioManager.instance.previous()),
            IconButton(
              onPressed: () async {
                bool playing = await AudioManager.instance.playOrPause();
                print("await -- $playing");
              },
              padding: const EdgeInsets.all(0.0),
              icon: Icon(
                isPlaying ? Icons.pause : Icons.play_arrow,
                size: 48.0,
                color: Colors.black,
              ),
            ),
            IconButton(
                iconSize: 36,
                icon: Icon(
                  Icons.skip_next,
                  color: Colors.black,
                ),
                onPressed: () => AudioManager.instance.next()),
            IconButton(
                icon: Icon(
                  Icons.stop,
                  color: Colors.black,
                ),
                onPressed: () => AudioManager.instance.stop()),
          ],
        ),
      ),
    ]);
  }

  Widget getPlayModeIcon(PlayMode playMode) {
    switch (playMode) {
      case PlayMode.sequence:
        return Icon(
          Icons.repeat,
          color: Colors.black,
        );
      case PlayMode.shuffle:
        return Icon(
          Icons.shuffle,
          color: Colors.black,
        );
      case PlayMode.single:
        return Icon(
          Icons.repeat_one,
          color: Colors.black,
        );
    }
    return Container();
  }

  Widget songProgress(BuildContext context) {
    var style = TextStyle(color: Colors.black);
    return Row(
      children: <Widget>[
        Text(
          _formatDuration(_position),
          style: style,
        ),
        Expanded(
          child: Padding(
            padding: EdgeInsets.symmetric(horizontal: 5),
            child: SliderTheme(
                data: SliderTheme.of(context).copyWith(
                  trackHeight: 2,
                  thumbColor: Colors.blueAccent,
                  overlayColor: Colors.blue,
                  thumbShape: RoundSliderThumbShape(
                    disabledThumbRadius: 5,
                    enabledThumbRadius: 5,
                  ),
                  overlayShape: RoundSliderOverlayShape(
                    overlayRadius: 10,
                  ),
                  activeTrackColor: Colors.blueAccent,
                  inactiveTrackColor: Colors.grey,
                ),
                child: Slider(
                  value: _slider ?? 0,
                  onChanged: (value) {
                    setState(() {
                      _slider = value;
                    });
                  },
                  onChangeEnd: (value) {
                    if (_duration != null) {
                      Duration msec = Duration(
                          milliseconds:
                              (_duration.inMilliseconds * value).round());
                      AudioManager.instance.seekTo(msec);
                    }
                  },
                )),
          ),
        ),
        Text(
          _formatDuration(_duration),
          style: style,
        ),
      ],
    );
  }

  String _formatDuration(Duration d) {
    if (d == null) return "--:--";
    int minute = d.inMinutes;
    int second = (d.inSeconds > 60) ? (d.inSeconds % 60) : d.inSeconds;
    String format = ((minute < 10) ? "0$minute" : "$minute") +
        ":" +
        ((second < 10) ? "0$second" : "$second");
    return format;
  }

  Widget volumeFrame() {
    return Row(children: <Widget>[
      IconButton(
          padding: EdgeInsets.all(0),
          icon: Icon(
            Icons.audiotrack,
            color: Colors.black,
          ),
          onPressed: () {
            AudioManager.instance.setVolume(0);
          }),
      Expanded(
          child: Padding(
              padding: EdgeInsets.symmetric(horizontal: 0),
              child: Slider(
                value: _sliderVolume ?? 0,
                onChanged: (value) {
                  setState(() {
                    _sliderVolume = value;
                    AudioManager.instance.setVolume(value, showVolume: true);
                  });
                },
              )))
    ]);
  }
}

这个示例代码创建了一个简单的音频播放器应用,其中包括了对本地资源、网络资源以及文件系统的支持。通过AudioManager的事件监听机制,你可以轻松地控制音频的播放、暂停、停止等功能,并且能够响应各种播放状态的变化。


更多关于Flutter音频管理插件audio_manager的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter音频管理插件audio_manager的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何使用Flutter音频管理插件audio_manager的示例代码。audio_manager插件允许你管理和控制设备的音频会话,例如设置音频模式、锁定音频焦点等。以下是一个简单的示例,展示如何使用该插件来设置音频模式并锁定音频焦点。

首先,确保你已经在pubspec.yaml文件中添加了audio_manager依赖:

dependencies:
  flutter:
    sdk: flutter
  audio_manager: ^0.8.0  # 请检查最新版本号

然后,运行flutter pub get来安装依赖。

接下来,在你的Flutter应用中,你可以这样使用audio_manager

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Audio Manager Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: AudioManagerDemo(),
    );
  }
}

class AudioManagerDemo extends StatefulWidget {
  @override
  _AudioManagerDemoState createState() => _AudioManagerDemoState();
}

class _AudioManagerDemoState extends State<AudioManagerDemo> {
  late AudioManager audioManager;

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

  void initAudioManager() async {
    audioManager = AudioManager();

    // 设置音频模式为通信模式(适用于VoIP应用)
    await audioManager.setMode(AudioMode.communication);

    // 请求音频焦点
    AudioFocusRequest request = AudioFocusRequest.builder()
      ..setAudioAttributes(
        AudioAttributes.builder()
          ..setContentType(AudioContentType.speech)
          ..setUsage(AudioUsage.voiceCommunication)
          .build(),
      )
      ..setAcceptsDelayedFocusGain(true)
      ..setOnAudioFocusChangeListener((AudioFocusRequest request, AudioFocusState state) {
        print('Audio focus changed: $state');
      })
      .build();

    await audioManager.requestAudioFocus(request);

    // 监听音频会话状态变化
    audioManager.sessionState.listen((sessionState) {
      print('Audio session state changed: $sessionState');
    });
  }

  @override
  void dispose() {
    // 释放音频焦点
    audioManager.abandonAudioFocus();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Audio Manager Demo'),
      ),
      body: Center(
        child: Text('Audio Manager is initialized and configured'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          // 示例:播放一个简短的音频文件(需要自行添加音频文件到项目资源中)
          // 注意:这里只是示例,实际播放音频可能需要使用其他插件如audioplayers
          // AudioPlayer audioPlayer = AudioPlayer();
          // await audioPlayer.play(AudioSource.fromAsset('assets/audio/sample.mp3'));

          // 在此示例中,我们只打印一条消息
          print('Audio setup is complete. You can add your audio playback code here.');
        },
        tooltip: 'Play Audio',
        child: Icon(Icons.play_arrow),
      ),
    );
  }
}

请注意:

  1. 音频播放:上述代码中的音频播放部分被注释掉了,因为audio_manager插件本身不处理音频播放。你可以使用其他插件如audioplayers来处理音频文件的播放。
  2. 音频焦点:在请求音频焦点时,我们设置了一个监听器来监听音频焦点的变化。
  3. 音频会话状态:我们还监听了音频会话状态的变化,这有助于了解音频会话的当前状态。

确保在实际应用中处理异常和边缘情况,例如请求音频焦点失败时的处理。

回到顶部