Flutter音频服务管理插件audio_service_13的使用

Flutter音频服务管理插件audio_service_13的使用

audio_service_13 是基于 @defsub 的拉取请求和 https://github.com/ryanheise/audio_service/tree/minor 分支的一个 audio_service 插件的分支版本。该插件允许在 Android 13 中添加快进和倒回按钮。当前插件存在的问题是 Android SDK 33 的 API 改变了自定义动作的添加方式,这导致现有代码失效。此插件是基于以下拉取请求:https://github.com/ryanheise/audio_service/pull/973。该插件托管于 https://github.com/dra11y/audio_service_13/tree/android-13

该插件可以将您的现有音频代码封装起来,使其能够在后台运行或在屏幕关闭时运行,并且允许您的应用与耳机按钮、锁屏界面、通知栏、iOS 控制中心、可穿戴设备和 Android Auto 进行交互。它适用于:

  • 音乐播放器
  • 文本转语音阅读器
  • 播客播放器
  • 视频播放器
  • 导航器
  • 更多!

插件如何工作?

您可以在音频处理程序中封装您的音频代码,以实现标准回调,这些回调允许您的应用响应来自 Flutter UI、耳机按钮、锁屏界面、通知栏、iOS 控制中心、车载显示器和智能手表的播放请求,即使应用处于后台状态。

audio_handler

您可以实现这些回调来播放适合您应用的任何类型的音频,例如音乐文件或流媒体、音频资源、文本转语音、合成音频或这些的组合。

功能 Android iOS macOS Web
背景音频
耳机点击
播放/暂停/跳过/调节速度/停止
快进/倒回
重复/随机模式
队列管理,跳过下一曲/上一曲
自定义动作/事件/状态
通知/控制中心
锁屏控制
专辑封面
Android Auto, Apple CarPlay

如果您希望帮助添加缺失的功能,请加入我们的 GitHub 问题页面。

教程和文档

  • @suragch 的文章《使用 Audio Service 和 Just Audio 实现 Flutter 的后台音频》
  • 教程:引导您构建一个简单的音频播放器并解释基本概念。
  • 完整示例:GitHub 上的 example 子目录演示了音乐和文本转语音用例。
  • 常见问题解答
  • API 文档

0.18.0 版本的新功能

0.18.0 版本移除了对后台隔离的需求,简化了您的 UI 和音频逻辑之间的通信,并提高了与其他不支持多个隔离的插件的兼容性。它还包含了许多其他新功能,详情请参阅 CHANGELOG

请参阅迁移指南以了解如何更新您的代码。

我可以在音频处理程序中使用其他插件吗?

是的!audio_service 允许您以任何方式实现音频逻辑,并使用任何您喜欢的插件。您可以使用您最喜欢的音频插件,如 just_audioflutter_tts 等,在您的音频处理程序中。还有一些插件如 just_audio_handlers 提供了默认的 AudioHandler 实现,使您的工作更轻松。

请注意,此插件不会与其他具有相同责任(即背景音频、iOS 控制中心、Android 通知、锁屏、耳机按钮等)的音频插件一起工作。

示例

初始化

定义您的 AudioHandler 并包含您希望您的应用处理的回调:

class MyAudioHandler extends BaseAudioHandler
    with QueueHandler, // 混入默认队列回调实现
    SeekHandler { // 混入默认跳转回调实现

  // 最常见的回调:
  Future<void> play() async {
    // 所有来自不同来源的播放请求都会路由到这里。实现此回调以开始播放适合您应用的音频,例如音乐。
  }
  Future<void> pause() async {}
  Future<void> stop() async {}
  Future<void> seek(Duration position) async {}
  Future<void> skipToQueueItem(int i) async {}
}

在应用程序启动期间注册您的 AudioHandler

Future<void> main() async {
  // 将此存储在单例中
  _audioHandler = await AudioService.init(
    builder: () => MyAudioHandler(),
    config: AudioServiceConfig(
      androidNotificationChannelId: 'com.mycompany.myapp.channel.audio',
      androidNotificationChannelName: 'Music playback',
    ),
  );
  runApp(new MyApp());
}

向音频处理程序发送请求

标准控制:

_audioHandler.play();
_audioHandler.seek(Duration(seconds: 10));
_audioHandler.setSpeed(1.5);
_audioHandler.pause();
_audioHandler.stop();

播放特定媒体项:

var item = MediaItem(
  id: 'https://example.com/audio.mp3',
  album: 'Album name',
  title: 'Track title',
  artist: 'Artist name',
  duration: const Duration(milliseconds: 123456),
  artUri: Uri.parse('https://example.com/album.jpg'),
);

_audioHandler.playMediaItem(item);
_audioHandler.playFromSearch(queryString);
_audioHandler.playFromUri(uri);
_audioHandler.playFromMediaId(id);

队列管理:

_audioHandler.addQueueItem(item);
_audioHandler.insertQueueItem(1, item);
_audioHandler.removeQueueItem(item);
_audioHandler.updateQueue([item, ...]);
_audioHandler.skipToNext();
_audioHandler.skipToPrevious();
_audioHandler.skipToQueueItem(2);

循环和随机播放:

_audioHandler.setRepeatMode(AudioServiceRepeatMode.one); // none/one/all/group
_audioHandler.setShuffleMode(AudioServiceShuffleMode.all); // none/all/group

自定义操作:

_audioHandler.customAction('setVolume', {'volume': 0.8});
_audioHandler.customAction('saveBookmark');

广播状态更改

您的音频处理程序必须广播状态更改,以便系统通知和智能手表(等)知道应显示什么状态。您的应用的 Flutter UI 可能也会监听这些状态更改,以便知道应显示什么状态。因此,音频处理程序为所有客户端提供了单一的音频状态源。

广播当前媒体项:

class MyAudioHandler extends BaseAudioHandler ... {
    ...
    mediaItem.add(item1);
    ...

广播当前队列:

  ...
  queue.add(<MediaItem>[item1, item2, item3]);
  ...

广播当前播放状态:

    ...
    // 所有选项如下所示:
    playbackState.add(PlaybackState(
      // 在通知中现在应该出现哪些按钮
      controls: [
        MediaControl.skipToPrevious,
        MediaControl.pause,
        MediaControl.stop,
        MediaControl.skipToNext,
      ],
      // 在通知中应该启用哪些其他操作
      systemActions: const {
        MediaAction.seek,
        MediaAction.seekForward,
        MediaAction.seekBackward,
      },
      // 在 Android 的紧凑视图中应显示哪些控件。
      androidCompactActionIndices: const [0, 1, 3],
      // 音频是否已准备好,正在缓冲,...
      processingState: AudioProcessingState.ready,
      // 音频是否正在播放
      playing: true,
      // 当前位置作为此次更新的一部分。您不应该连续广播位置变化,因为监听器可以根据当前速度和音频是否正在播放和准备好来推断当前位置。相反,只有在它们与预期不符时才广播位置更新(例如,缓冲或跳转)。
      updatePosition: Duration(milliseconds: 54321),
      // 当前缓冲位置作为此次更新的一部分
      bufferedPosition: Duration(milliseconds: 65432),
      // 当前速度
      speed: 1.0,
      // 当前队列位置
      queueIndex: 0,
    ));

使用 copyWith 广播播放状态的变更:

    playbackState.add(playbackState.value.copyWith(
      // 保持所有现有状态不变,仅更改速度:
      speed: newSpeed,
    ));

监听状态更改

从 Flutter UI 监听当前播放项的变化:

_audioHandler.mediaItem.listen((MediaItem item) { ... });

从 Flutter UI 监听队列的变化:

_audioHandler.queue.listen((List<MediaItem> queue) { ... });

从 Flutter UI 监听播放状态的变化:

_audioHandler.playbackState.listen((PlaybackState state) {
  if (state.playing) ... else ...
  switch (state.processingState) {
    case AudioProcessingState.idle: ...
    case AudioProcessingState.loading: ...
    case AudioProcessingState.buffering: ...
    case AudioProcessingState.ready: ...
    case AudioProcessingState.completed: ...
    case AudioProcessingState.error: ...
  }
});

监听当前播放位置的连续变化:

AudioService.position.listen((Duration position) { ... });

高级功能

组合多个音频处理类:

_audioHandler = await AudioService.init(
  builder: () => AnalyticsAudioHandler(
    PersistingAudioHandler(
      MyAudioHandler())),
);

从另一个隔离中连接:

// 使用 IsolatedAudioHandler 包装音频处理程序:
_audioHandler = await AudioService.init(
  builder: () => IsolatedAudioHandler(
    MyAudioHandler(),
    portName: 'my_audio_handler',
  ),
);
// 从另一个隔离中获取代理引用:
_proxyAudioHandler = await IsolatedAudioHandler.lookup(
  portName: 'my_audio_handler',
);

查看完整示例以了解如何处理队列/播放列表、耳机按钮点击、媒体艺术和文本转语音。

配置音频会话

如果您的应用使用音频,您应该告诉操作系统您的应用具有什么样的使用场景,以及您的应用将如何与其他音频应用程序在设备上交互。不同的音频应用程序通常有不同的要求。例如,当导航应用程序播报驾驶指示时,音乐播放器应该降低音量,而播客播放器应该暂停播放。根据您正在构建的应用类型,您需要配置您的应用程序的音频设置和回调以适当地处理这些交互。

使用 audio_session 包更改您的应用的默认音频会话配置。例如,对于播客播放器,您可以使用:

final session = await AudioSession.instance;
await session.configure(AudioSessionConfiguration.speech());

每次您调用音频插件播放音频时,该插件都会激活您的应用程序共享的音频会话,以通知操作系统您的应用程序正在积极播放音频。根据上面设置的配置,这也将告知其他音频应用程序停止播放音频,或者可能继续以较低的音量播放(即,压低音量)。您通常不需要自己激活音频会话,但是,如果使用的音频插件没有激活音频会话,则您可以自己激活它:

// 在播放音频之前激活音频会话。
if (await session.setActive(true)) {
  // 现在播放音频。
} else {
  // 请求被拒绝,应用程序不应播放音频
}

当另一个应用程序激活其音频会话时,它也可能要求您的应用程序暂停或压低音量。同样,您使用的特定音频插件可能会自动暂停或压低音量。但是,如果没有,您可以自己通过监听 session.interruptionEventStream 来响应这些事件。类似地,如果音频插件不处理拔出耳机事件,您可以通过监听 session.becomingNoisyEventStream 来响应这些事件。有关更多信息,请查阅 audio_session 的文档。

注意:如果您的应用程序使用多种不同的音频插件,例如用于音频录制或文本转语音或后台音频,那么这些插件可能会内部覆盖彼此的音频会话设置,因为只有一个音频会话由您的应用程序共享。因此,建议您在所有其他音频插件加载之后使用 audio_session 应用您自己的首选配置。您可以考虑询问您使用的每个音频插件的开发者提供一个选项,不覆盖这些全局设置,并允许它们外部管理。

Android 设置

这些说明假定您的项目遵循 Flutter 1.12 项目的模板或更高版本。如果您的项目是在 1.12 之前创建的并且使用旧的项目结构,您可以更新您的项目以遵循新的项目模板。

此外:

  1. 对您的项目的 AndroidManifest.xml 文件进行以下更改:
<manifest xmlns:tools="http://schemas.android.com/tools" ...>
  <!-- 添加这两个权限 -->
  <uses-permission android:name="android.permission.WAKE_LOCK"/>
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
  
  <application ...>
    
    ...
    
    <!-- 编辑现有的 "ACTIVITY" 元素中的 android:name 属性 -->
    <activity android:name="com.ryanheise.audioservice.AudioServiceActivity" ...>
      ...
    </activity>
    
    <!-- 添加这个 "SERVICE" 元素 -->
    <service android:name="com.ryanheise.audioservice.AudioService"
        android:foregroundServiceType="mediaPlayback"
        android:exported="true" tools:ignore="Instantiable">
      <intent-filter>
        <action android:name="android.media.browse.MediaBrowserService" />
      </intent-filter>
    </service>

    <!-- 添加这个 "RECEIVER" 元素 -->
    <receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
        android:exported="true" tools:ignore="Instantiable">
      <intent-filter>
        <action android:name="android.intent.action.MEDIA_BUTTON" />
      </intent-filter>
    </receiver> 
  </application>
</manifest>

注意:当目标 Android 12 或以上时,您必须在每个具有意图过滤器的组件上设置 android:exported。如果合并过程导致 Instantiable 静态警告,请使用 tools:ignore="Instantiable" 抑制它们。

  1. 如果您在通知中使用任何自定义图标,请创建文件 android/app/src/main/res/raw/keep.xml 以防止它们在构建过程中被剥离:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
  tools:keep="@drawable/*" />

默认情况下,插件的默认图标不会被 R8 剥离。如果您不使用它们,您可以选择性地剥离它们。例如,以下规则将保留您所有的图标并丢弃插件的所有图标:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
  tools:keep="@drawable/*"
  tools:discard="@drawable/audio_service_*" 
/>

有关缩小的信息,请参阅 Android 文档。

自定义 Android 活动

如果您的应用程序需要使用自己的自定义活动,请确保您在 AndroidManifest.xml 文件中引用您的活动类名称而不是 AudioServiceActivity。例如,如果您的活动类名为 MainActivity,则使用:

    <activity android:name=".MainActivity" ...>

根据您的活动是一个常规的 Activity 还是一个 FragmentActivity,您还需要包括一些代码来链接到 audio_service 的共享 FlutterEngine。最简单的方法是继承 audio_service 提供的基类之一中的代码。

  1. 作为 Activity 集成:
import com.ryanheise.audioservice.AudioServiceActivity;

class MainActivity extends AudioServiceActivity {
    // ...
}
  1. 作为 FragmentActivity 集成:
import com.ryanheise.audioservice.AudioServiceFragmentActivity;

class MainActivity extends AudioServiceFragmentActivity {
    // ...
}

您也可以从头编写自己的活动类,并自行覆盖 provideFlutterEnginegetCachedEngineIdshouldDestroyEngineWithHost 方法。有关灵感,请参阅提供的 AudioServiceActivityAudioServiceFragmentActivity 类的源代码。

iOS 设置

在您的 Info.plist 文件中插入以下内容:

  <key>UIBackgroundModes</key>
  <array>
    <string>audio</string>
  </array>

示例项目可用于上下文参考。

请注意,audio 背景模式仅允许应用程序为了播放音频而在后台运行。操作系统可能会在应用程序无故闲置时不播放音频时将其进程杀死。例如,如果您的应用程序需要在音频片段之间暂停几秒钟,请考虑播放无声音频片段来达到这种效果,而不是使用空闲计时器。

macOS 设置

支持的最低 macOS 版本是 10.12.2(尽管这可能会在未来的工作中改变)。修改 macos/Podfile 中的平台行,使其看起来像下面这样:

platform :osx, '10.12.2'

示例代码

// ignore_for_file: public_member_api_docs

// FOR MORE EXAMPLES, VISIT THE GITHUB REPOSITORY AT:
//
//  https://github.com/ryanheise/audio_service
//
// This example implements a minimal audio handler that renders the current
// media item and playback state to the system notification and responds to 4
// media actions:
//
// - play
// - pause
// - seek
// - stop
//
// To run this example, use:
//
// flutter run

import 'dart:async';

import 'package:audio_service_13/audio_service_13.dart';
import 'package:audio_service_example/common.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:rxdart/rxdart.dart';

// You might want to provide this using dependency injection rather than a
// global variable.
late AudioHandler _audioHandler;

Future<void> main() async {
  _audioHandler = await AudioService.init(
    builder: () => AudioPlayerHandler(),
    config: const AudioServiceConfig(
      androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
      androidNotificationChannelName: 'Audio playback',
      androidNotificationOngoing: true,
    ),
  );
  runApp(const MyApp());
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Audio Service Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MainScreen(),
    );
  }
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Audio Service Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 显示媒体项标题
            StreamBuilder<MediaItem?>(
              stream: _audioHandler.mediaItem,
              builder: (context, snapshot) {
                final mediaItem = snapshot.data;
                return Text(mediaItem?.title ?? '');
              },
            ),
            // 播放/暂停/停止按钮。
            StreamBuilder<bool>(
              stream: _audioHandler.playbackState
                  .map((state) => state.playing)
                  .distinct(),
              builder: (context, snapshot) {
                final playing = snapshot.data ?? false;
                return Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    _button(Icons.fast_rewind, _audioHandler.rewind),
                    if (playing)
                      _button(Icons.pause, _audioHandler.pause)
                    else
                      _button(Icons.play_arrow, _audioHandler.play),
                    _button(Icons.stop, _audioHandler.stop),
                    _button(Icons.fast_forward, _audioHandler.fastForward),
                  ],
                );
              },
            ),
            // 一个进度条。
            StreamBuilder<MediaState>(
              stream: _mediaStateStream,
              builder: (context, snapshot) {
                final mediaState = snapshot.data;
                return SeekBar(
                  duration: mediaState?.mediaItem?.duration ?? Duration.zero,
                  position: mediaState?.position ?? Duration.zero,
                  onChangeEnd: (newPosition) {
                    _audioHandler.seek(newPosition);
                  },
                );
              },
            ),
            // 显示处理状态。
            StreamBuilder<AudioProcessingState>(
              stream: _audioHandler.playbackState
                  .map((state) => state.processingState)
                  .distinct(),
              builder: (context, snapshot) {
                final processingState =
                    snapshot.data ?? AudioProcessingState.idle;
                return Text(
                    "处理状态: ${describeEnum(processingState)}");
              },
            ),
          ],
        ),
      ),
    );
  }

  /// 一个报告当前媒体项及其当前位置的组合状态的流。
  Stream<MediaState> get _mediaStateStream =>
      Rx.combineLatest2<MediaItem?, Duration, MediaState>(
          _audioHandler.mediaItem,
          AudioService.position,
          (mediaItem, position) => MediaState(mediaItem, position));

  IconButton _button(IconData iconData, VoidCallback onPressed) => IconButton(
        icon: Icon(iconData),
        iconSize: 64.0,
        onPressed: onPressed,
      );
}

class MediaState {
  final MediaItem? mediaItem;
  final Duration position;

  MediaState(this.mediaItem, this.position);
}

/// 一个播放单个项目的 [AudioHandler]。
class AudioPlayerHandler extends BaseAudioHandler with SeekHandler {
  static final _item = MediaItem(
    id: 'https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3',
    album: "Science Friday",
    title: "A Salute To Head-Scratching Science",
    artist: "Science Friday and WNYC Studios",
    duration: const Duration(milliseconds: 5739820),
    artUri: Uri.parse(
        'https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg'),
  );

  final _player = AudioPlayer();

  /// 初始化我们的音频处理器。
  AudioPlayerHandler() {
    // 为了让我们的客户端(Flutter UI 和系统通知)知道应显示什么状态,我们在这里设置了音频处理器以广播所有播放状态变化。
    _player.playbackEventStream.map(_transformEvent).pipe(playbackState);
    // ... 并且也通过媒体项广播当前媒体项。
    mediaItem.add(_item);

    // 加载播放器。
    _player.setAudioSource(AudioSource.uri(Uri.parse(_item.id)));
  }

  // 在这个简单的例子中,我们只处理了 4 个操作:播放、暂停、跳过和停止。任何来自 Flutter UI、通知、锁屏或耳机的按钮按下都会被路由到这 4 个方法,因此您可以在一个地方处理您的音频播放逻辑。

  [@override](/user/override)
  Future<void> play() => _player.play();

  [@override](/user/override)
  Future<void> pause() => _player.pause();

  [@override](/user/override)
  Future<void> seek(Duration position) => _player.seek(position);

  [@override](/user/override)
  Future<void> stop() => _player.stop();

  /// 将一个 just_audio 事件转换为 audio_service 状态。
  ///
  /// 此方法在构造函数中使用。从 just_audio 播放器接收到的每个事件都会被转换为 audio_service 状态,以便它可以广播给 audio_service 客户端。
  PlaybackState _transformEvent(PlaybackEvent event) {
    return PlaybackState(
      controls: [
        MediaControl.rewind,
        if (_player.playing) MediaControl.pause else MediaControl.play,
        MediaControl.stop,
        MediaControl.fastForward,
      ],
      systemActions: const {
        MediaAction.seek,
        MediaAction.seekForward,
        MediaAction.seekBackward,
      },
      androidCompactActionIndices: const [0, 1, 3],
      processingState: const {
        ProcessingState.idle: AudioProcessingState.idle,
        ProcessingState.loading: AudioProcessingState.loading,
        ProcessingState.buffering: AudioProcessingState.buffering,
        ProcessingState.ready: AudioProcessingState.ready,
        ProcessingState.completed: AudioProcessingState.completed,
      }[_player.processingState]!,
      playing: _player.playing,
      updatePosition: _player.position,
      bufferedPosition: _player.bufferedPosition,
      speed: _player.speed,
      queueIndex: event.currentIndex,
    );
  }
}

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

1 回复

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


audio_service 是一个用于在 Flutter 应用中管理音频播放的插件。它允许你在后台播放音频,并且可以与系统媒体控制(例如锁屏界面上的播放控制)进行交互。audio_service 提供了一个抽象层,使得你可以轻松地管理音频播放、暂停、快进、快退等操作。

以下是使用 audio_service 的基本步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 audio_service 依赖:

dependencies:
  flutter:
    sdk: flutter
  audio_service: ^0.18.7

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

2. 创建音频处理程序

你需要创建一个继承自 AudioHandler 的类来处理音频播放逻辑。这个类将负责与 audio_service 进行交互。

import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';

class AudioPlayerHandler extends BaseAudioHandler {
  final _player = AudioPlayer();

  AudioPlayerHandler() {
    // 监听播放状态
    _player.playbackEventStream.map(_transformEvent).pipe(playbackState);

    // 监听媒体项
    _player.currentIndexStream.listen((index) {
      if (index != null) {
        mediaItem.add(_mediaItems[index]);
      }
    });
  }

  [@override](/user/override)
  Future<void> play() => _player.play();

  [@override](/user/override)
  Future<void> pause() => _player.pause();

  [@override](/user/override)
  Future<void> stop() => _player.stop();

  [@override](/user/override)
  Future<void> seek(Duration position) => _player.seek(position);

  [@override](/user/override)
  Future<void> skipToQueueItem(int index) async {
    _player.seek(Duration.zero, index: index);
  }

  [@override](/user/override)
  Future<void> skipToNext() => _player.seekToNext();

  [@override](/user/override)
  Future<void> skipToPrevious() => _player.seekToPrevious();

  [@override](/user/override)
  Future<void> setSpeed(double speed) => _player.setSpeed(speed);

  PlaybackState _transformEvent(PlaybackEvent event) {
    return PlaybackState(
      controls: [
        MediaControl.skipToPrevious,
        if (_player.playing)
          MediaControl.pause
        else
          MediaControl.play,
        MediaControl.skipToNext,
      ],
      systemActions: const {
        MediaAction.seek,
        MediaAction.seekForward,
        MediaAction.seekBackward,
      },
      androidCompactActionIndices: const [0, 1, 2],
      processingState: const {
        ProcessingState.idle: AudioProcessingState.idle,
        ProcessingState.loading: AudioProcessingState.loading,
        ProcessingState.buffering: AudioProcessingState.buffering,
        ProcessingState.ready: AudioProcessingState.ready,
        ProcessingState.completed: AudioProcessingState.completed,
      }[_player.processingState]!,
      playing: _player.playing,
      updatePosition: _player.position,
      bufferedPosition: _player.bufferedPosition,
      speed: _player.speed,
      queueIndex: event.currentIndex,
    );
  }

  final _mediaItems = [
    MediaItem(
      id: 'https://example.com/audio1.mp3',
      title: 'Audio 1',
      artist: 'Artist 1',
    ),
    MediaItem(
      id: 'https://example.com/audio2.mp3',
      title: 'Audio 2',
      artist: 'Artist 2',
    ),
  ];
}

3. 初始化 AudioService

在你的 main.dart 文件中,初始化 AudioService 并启动它。

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

import 'audio_player_handler.dart';

void main() async {
  // 确保 Flutter 初始化完成
  WidgetsFlutterBinding.ensureInitialized();

  // 初始化 AudioService
  await AudioService.init(
    builder: () => AudioPlayerHandler(),
    config: AudioServiceConfig(
      androidNotificationChannelId: 'com.example.myapp.audio',
      androidNotificationChannelName: 'My App Audio',
      androidNotificationOngoing: true,
    ),
  );

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: AudioPlayerScreen(),
    );
  }
}

4. 构建 UI

创建一个简单的 UI 来控制音频播放。

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

class AudioPlayerScreen extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Audio Player'),
      ),
      body: Center(
        child: StreamBuilder<PlaybackState>(
          stream: AudioService.playbackStateStream,
          builder: (context, snapshot) {
            final playbackState = snapshot.data;
            final isPlaying = playbackState?.playing ?? false;
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton(
                  icon: Icon(isPlaying ? Icons.pause : Icons.play_arrow),
                  onPressed: () {
                    if (isPlaying) {
                      AudioService.pause();
                    } else {
                      AudioService.play();
                    }
                  },
                ),
                IconButton(
                  icon: Icon(Icons.stop),
                  onPressed: AudioService.stop,
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}
回到顶部