Flutter音频播放插件simple_audio_fork的使用
Flutter音频播放插件simple_audio_fork的使用
Simple Audio Fork
是一个用于在 Flutter 中播放音频的跨平台解决方案。它旨在提供简单且稳定的 API,支持多种功能,包括本地和在线资源的播放、无间隙播放、音量规范化等。虽然这是一个分叉版本,但其功能与原始插件基本一致。
特性
- 简单的 API
- 跨平台支持(Android、Linux、Windows、iOS、macOS)
- 媒体控制器支持
- Linux: MPRIS
- Android: MediaSessionCompat
- Windows: SystemMediaTransportControls
- iOS/macOS: 控制中心
- 支持本地和在线资源播放
- 无间隙播放和预加载
- 音量规范化
文档
该插件的文档托管在 pub.dev 上,可以在以下链接找到:simple_audio 文档。
使用方法
1. 添加依赖
首先,在 pubspec.yaml
文件中添加 simple_audio
作为依赖:
dependencies:
simple_audio: ^最新版本号
然后运行 flutter pub get
来安装依赖。
2. 初始化插件
在应用启动时调用 SimpleAudio.init()
方法进行初始化。以下是初始化的示例代码:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化插件,默认值
await SimpleAudio.init(
useMediaController: true, // 是否启用媒体控制器
shouldNormalizeVolume: false, // 是否启用音量规范化
dbusName: "com.erikas.SimpleAudio", // D-Bus 名称
actions: [
MediaControlAction.rewind, // 倒退
MediaControlAction.skipPrev, // 上一首
MediaControlAction.playPause, // 播放/暂停
MediaControlAction.skipNext, // 下一首
MediaControlAction.fastForward, // 快进
],
androidNotificationIconPath: "mipmap/ic_launcher", // Android 通知图标路径
androidCompactActions: [1, 2, 3], // Android 通知按钮
applePreferSkipButtons: true, // iOS 是否优先使用跳过按钮
);
runApp(const MyApp());
}
3. 创建播放器实例
在需要播放音频的地方创建 SimpleAudio
实例,并监听事件。以下是创建播放器的示例代码:
class _MyAppState extends State<MyApp> {
final SimpleAudio player = SimpleAudio(
onSkipNext: (_) => debugPrint("Next"), // 上一首回调
onSkipPrevious: (_) => debugPrint("Prev"), // 下一首回调
onNetworkStreamError: (player, error) { // 网络流错误回调
debugPrint("Network Stream Error: $error");
player.stop();
},
onDecodeError: (player, error) { // 解码错误回调
debugPrint("Decode Error: $error");
player.stop();
},
);
PlaybackState playbackState = PlaybackState.stop; // 当前播放状态
bool get isPlaying => playbackState == PlaybackState.play || playbackState == PlaybackState.preloadPlayed; // 是否正在播放
bool get isMuted => volume == 0; // 是否静音
double trueVolume = 1; // 实际音量
double volume = 1; // 当前音量
bool normalize = false; // 是否启用音量规范化
bool loop = false; // 是否循环播放
double position = 0; // 当前播放位置
double duration = 0; // 音频总时长
// 将秒数转换为可读格式
String convertSecondsToReadableString(int seconds) {
int m = seconds ~/ 60;
int s = seconds % 60;
return "$m:${s > 9 ? s : "0$s"}";
}
// 选择文件
Future<String> pickFile() async {
FilePickerResult? file = await FilePicker.platform.pickFiles(
dialogTitle: "Pick file to play.",
type: FileType.audio,
);
final PlatformFile pickedFile = file!.files.single;
return pickedFile.path!;
}
[@override](/user/override)
void initState() {
super.initState();
// 监听播放状态变化
player.playbackStateStream.listen((event) {
setState(() => playbackState = event);
});
// 监听播放进度变化
player.progressStateStream.listen((event) {
setState(() {
position = event.position.toDouble();
duration = event.duration.toDouble();
});
});
}
}
4. 操作播放器
通过调用 SimpleAudio
提供的方法来控制音频播放。以下是一些常用的播放操作示例:
ElevatedButton(
child: const Text("Open File"),
onPressed: () async {
String path = await pickFile();
// 设置元数据
await player.setMetadata(
const Metadata(
title: "Title",
artist: "Artist",
album: "Album",
artUri: "https://picsum.photos/200",
),
);
// 停止当前播放并打开新文件
await player.stop();
await player.open(path);
},
),
5. 播放预加载的文件
除了直接播放文件外,还可以预加载音频文件以便后续播放:
ElevatedButton(
child: const Text("Preload File"),
onPressed: () async {
String path = await pickFile();
await player.preload(path); // 预加载文件
},
),
ElevatedButton(
child: const Text("Play Preload"),
onPressed: () async {
if (!await player.hasPreloaded) {
debugPrint("No preloaded file to play!");
return;
}
debugPrint("Playing preloaded file.");
await player.stop();
await player.playPreload(); // 播放预加载文件
},
),
ElevatedButton(
child: const Text("Clear Preload"),
onPressed: () async {
if (!await player.hasPreloaded) {
debugPrint("No preloaded file to clear!");
return;
}
debugPrint("Cleared preloaded file.");
await player.clearPreload(); // 清除预加载文件
},
),
6. 控制播放状态
可以通过按钮或滑块控制播放状态,例如停止、播放/暂停、调整音量等:
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleButton(
size: 35,
onPressed: playbackState != PlaybackState.done ? player.stop : null,
child: const Icon(Icons.stop, color: Colors.white),
),
const SizedBox(width: 10),
CircleButton(
size: 40,
onPressed: () {
if (isPlaying) {
player.pause();
} else {
player.play();
}
},
child: Icon(
isPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded,
color: Colors.white,
),
),
const SizedBox(width: 10),
CircleButton(
size: 35,
onPressed: () {
if (!isMuted) {
player.setVolume(0);
setState(() => volume = 0);
} else {
player.setVolume(trueVolume);
setState(() => volume = trueVolume);
}
},
child: Icon(
isMuted ? Icons.volume_off : Icons.volume_up,
color: Colors.white,
),
),
],
),
平台特定设置
Windows
无需额外设置。
Linux
在 linux/my_application.cc
文件中添加以下代码以支持 MPRIS:
static void my_application_activate(GApplication* application) {
GList* windows = gtk_application_get_windows(GTK_APPLICATION(application));
if(windows) {
gtk_window_present_with_time(
GTK_WINDOW(windows->data),
g_get_monotonic_time() / 1000
);
return;
}
MyApplication* self = MY_APPLICATION(application);
// ...
}
MyApplication* my_application_new() {
return MY_APPLICATION(g_object_new(my_application_get_type(),
"application-id", APPLICATION_ID, "flags",
G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN,
nullptr
));
}
Android
编辑 android/app/src/main/AndroidManifest.xml
文件,添加以下内容:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<service
android:name="com.erikas.simple_audio.SimpleAudioService"
android:foregroundServiceType="mediaPlayback"
android:exported="false">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<receiver
android:name="com.erikas.simple_audio.SimpleAudioReceiver" />
iOS
在 Xcode 中添加 AudioToolbox.framework
,并在 Info.plist
中添加以下内容:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
macOS
将 macOS 的最低部署版本更新为 10.13
:
- 打开
macos/Runner.xcworkspace
。 - 在项目设置中将
macOS Deployment Target
和Minimum Deployments
macOS 版本设置为10.13
。
完整示例代码
以下是一个完整的示例代码,展示了如何使用 Simple Audio Fork
插件播放音频:
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:file_picker/file_picker.dart';
import 'package:simple_audio/simple_audio.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 初始化插件
await SimpleAudio.init(
useMediaController: true,
shouldNormalizeVolume: false,
dbusName: "com.erikas.SimpleAudio",
actions: [
MediaControlAction.rewind,
MediaControlAction.skipPrev,
MediaControlAction.playPause,
MediaControlAction.skipNext,
MediaControlAction.fastForward,
],
androidNotificationIconPath: "mipmap/ic_launcher",
androidCompactActions: [1, 2, 3],
applePreferSkipButtons: true,
);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
[@override](/user/override)
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final SimpleAudio player = SimpleAudio(
onSkipNext: (_) => debugPrint("Next"),
onSkipPrevious: (_) => debugPrint("Prev"),
onNetworkStreamError: (player, error) {
debugPrint("Network Stream Error: $error");
player.stop();
},
onDecodeError: (player, error) {
debugPrint("Decode Error: $error");
player.stop();
},
);
PlaybackState playbackState = PlaybackState.stop;
bool get isPlaying => playbackState == PlaybackState.play || playbackState == PlaybackState.preloadPlayed;
bool get isMuted => volume == 0;
double trueVolume = 1;
double volume = 1;
bool normalize = false;
bool loop = false;
double position = 0;
double duration = 0;
String convertSecondsToReadableString(int seconds) {
int m = seconds ~/ 60;
int s = seconds % 60;
return "$m:${s > 9 ? s : "0$s"}";
}
Future<String> pickFile() async {
FilePickerResult? file = await FilePicker.platform.pickFiles(
dialogTitle: "Pick file to play.",
type: FileType.audio,
);
final PlatformFile pickedFile = file!.files.single;
return pickedFile.path!;
}
[@override](/user/override)
void initState() {
super.initState();
player.playbackStateStream.listen((event) {
setState(() => playbackState = event);
});
player.progressStateStream.listen((event) {
setState(() {
position = event.position.toDouble();
duration = event.duration.toDouble();
});
});
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Simple Audio Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (Platform.isAndroid || Platform.isIOS) ...{
Builder(
builder: (context) => ElevatedButton(
child: const Text("Get Storage Perms"),
onPressed: () async {
PermissionStatus status = await Permission.storage.request();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Storage Permissions: ${status.name}"),
),
);
},
),
),
},
const SizedBox(height: 5),
ElevatedButton(
child: const Text("Open File"),
onPressed: () async {
String path = await pickFile();
await player.setMetadata(
const Metadata(
title: "Title",
artist: "Artist",
album: "Album",
artUri: "https://picsum.photos/200",
),
);
await player.stop();
await player.open(path);
},
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
child: const Text("Preload File"),
onPressed: () async {
String path = await pickFile();
await player.preload(path);
},
),
const SizedBox(width: 5),
ElevatedButton(
child: const Text("Play Preload"),
onPressed: () async {
if (!await player.hasPreloaded) {
debugPrint("No preloaded file to play!");
return;
}
debugPrint("Playing preloaded file.");
await player.stop();
await player.playPreload();
},
),
const SizedBox(width: 5),
ElevatedButton(
child: const Text("Clear Preload"),
onPressed: () async {
if (!await player.hasPreloaded) {
debugPrint("No preloaded file to clear!");
return;
}
debugPrint("Cleared preloaded file.");
await player.clearPreload();
},
),
],
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircleButton(
size: 35,
onPressed: playbackState != PlaybackState.done ? player.stop : null,
child: const Icon(Icons.stop, color: Colors.white),
),
const SizedBox(width: 10),
CircleButton(
size: 40,
onPressed: () {
if (isPlaying) {
player.pause();
} else {
player.play();
}
},
child: Icon(
isPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded,
color: Colors.white,
),
),
const SizedBox(width: 10),
CircleButton(
size: 35,
onPressed: () {
if (!isMuted) {
player.setVolume(0);
setState(() => volume = 0);
} else {
player.setVolume(trueVolume);
setState(() => volume = trueVolume);
}
},
child: Icon(
isMuted ? Icons.volume_off : Icons.volume_up,
color: Colors.white,
),
),
],
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("Volume: "),
SizedBox(
width: 200,
child: Slider(
value: volume,
onChanged: (value) {
setState(() {
volume = value;
trueVolume = value;
});
player.setVolume(value);
},
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(
value: loop,
onChanged: (value) {
setState(() => loop = value!);
player.loopPlayback(loop);
},
),
const Text("Loop Playback"),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(
value: normalize,
onChanged: (value) {
setState(() => normalize = value!);
player.normalizeVolume(normalize);
},
),
const Text("Normalize Volume"),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(convertSecondsToReadableString(position.floor())),
Flexible(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 450),
child: Slider(
value: min(position, duration),
max: duration,
onChanged: (value) {
player.seek(value.floor());
},
),
),
),
Text(convertSecondsToReadableString(duration.floor())),
],
),
),
],
),
),
),
);
}
}
class CircleButton extends StatelessWidget {
const CircleButton({
required this.onPressed,
required this.child,
this.size = 35,
this.color = Colors.blue,
super.key,
});
final void Function()? onPressed;
final Widget child;
final double size;
final Color color;
[@override](/user/override)
Widget build(BuildContext context) {
return SizedBox(
height: size,
width: size,
child: ClipOval(
child: Material(
color: color,
child: InkWell(
canRequestFocus: false,
onTap: onPressed,
child: child,
),
),
),
);
}
}
更多关于Flutter音频播放插件simple_audio_fork的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter音频播放插件simple_audio_fork的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
simple_audio_fork
是一个 Flutter 插件,用于在 Flutter 应用中播放音频。它是 simple_audio
的一个分支版本,提供了简单的 API 来播放、暂停、停止和控制音频播放。以下是如何在 Flutter 项目中使用 simple_audio_fork
的基本步骤。
1. 添加依赖
首先,在 pubspec.yaml
文件中添加 simple_audio_fork
依赖:
dependencies:
flutter:
sdk: flutter
simple_audio_fork: ^1.0.0 # 请检查最新版本
然后运行 flutter pub get
来安装依赖。
2. 导入插件
在需要使用音频播放功能的 Dart 文件中导入 simple_audio_fork
:
import 'package:simple_audio_fork/simple_audio_fork.dart';
3. 初始化音频播放器
在使用音频播放器之前,需要先初始化它:
final audioPlayer = SimpleAudio();
4. 播放音频
使用 play
方法来播放音频。你可以传递本地文件路径或网络 URL:
await audioPlayer.play('assets/audio/sample.mp3'); // 播放本地文件
// 或者
await audioPlayer.play('https://example.com/sample.mp3'); // 播放网络音频
5. 控制播放
你可以使用以下方法来控制音频播放:
-
暂停播放:
await audioPlayer.pause();
-
继续播放:
await audioPlayer.resume();
-
停止播放:
await audioPlayer.stop();
-
跳转到指定位置:
await audioPlayer.seek(Duration(seconds: 30)); // 跳转到30秒处
6. 监听播放状态
你可以监听音频播放的状态,例如播放进度、播放完成等:
audioPlayer.onPlayerStateChanged.listen((state) {
print('Player state: $state');
});
audioPlayer.onPositionChanged.listen((position) {
print('Current position: $position');
});
audioPlayer.onDurationChanged.listen((duration) {
print('Total duration: $duration');
});
7. 释放资源
在不再需要音频播放器时,释放资源:
await audioPlayer.dispose();
8. 处理错误
你可以监听错误事件来处理播放过程中可能出现的错误:
audioPlayer.onPlayerError.listen((error) {
print('Player error: $error');
});
完整示例
以下是一个完整的示例,展示了如何使用 simple_audio_fork
播放音频:
import 'package:flutter/material.dart';
import 'package:simple_audio_fork/simple_audio_fork.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: AudioPlayerScreen(),
);
}
}
class AudioPlayerScreen extends StatefulWidget {
[@override](/user/override)
_AudioPlayerScreenState createState() => _AudioPlayerScreenState();
}
class _AudioPlayerScreenState extends State<AudioPlayerScreen> {
final audioPlayer = SimpleAudio();
[@override](/user/override)
void initState() {
super.initState();
audioPlayer.onPlayerStateChanged.listen((state) {
print('Player state: $state');
});
}
[@override](/user/override)
void dispose() {
audioPlayer.dispose();
super.dispose();
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Simple Audio Fork Example'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
await audioPlayer.play('assets/audio/sample.mp3');
},
child: Text('Play'),
),
ElevatedButton(
onPressed: () async {
await audioPlayer.pause();
},
child: Text('Pause'),
),
ElevatedButton(
onPressed: () async {
await audioPlayer.resume();
},
child: Text('Resume'),
),
ElevatedButton(
onPressed: () async {
await audioPlayer.stop();
},
child: Text('Stop'),
),
],
),
),
);
}
}