flutter_radio_player
適用於 Flutter 的線上廣播播放器,支援背景播放、鎖定畫面控制以及 Android 與 iOS 的串流支援。
Flutter 音頻播放器,一個無需煩惱即可處理串流音訊的外掛程式
以下為英文專案原文快照,最新內容請造訪 GitHub。
logo
Pub Version Pub Likes Pub Points CI
A Flutter plugin for playing streaming radio with background playback, lock screen controls, and platform-native media integrations.
Example Player
| Android | iOS | |
|---|---|---|
| Support | SDK 21+ | iOS 14+ |
flutter pub add flutter_radio_player
import 'package:flutter_radio_player/flutter_radio_player.dart';
final player = FlutterRadioPlayer();
await player.initialize([
const RadioSource(url: 'https://example.com/stream'),
], playWhenReady: true);
Define your radio stations using the RadioSource model:
const sources = [
RadioSource(url: 'https://example.com/stream'),
RadioSource(
url: 'https://example.com/jazz',
title: 'Jazz FM',
artwork: 'assets/jazz_cover.jpg', // bundled asset
),
RadioSource(
url: 'https://example.com/rock',
title: 'Rock Radio',
artwork: 'https://example.com/rock_cover.png', // remote URL
),
];
await player.initialize(sources, playWhenReady: true);
| Field | Type | Description |
|---|---|---|
url |
String |
Stream URL (required) |
title |
String? |
Display name for lock screen |
artwork |
String? |
Asset path or URL for album artwork |
await player.play();
await player.pause();
await player.playOrPause(); // toggle
// Navigate sources
await player.nextSource();
await player.previousSource();
await player.jumpToSourceAtIndex(2);
// Volume (0.0 to 1.0)
await player.setVolume(0.8);
final volume = await player.getVolume();
// Clean up when done
await player.dispose();
Listen to real-time player state changes using streams:
player.isPlayingStream.listen((bool isPlaying) {
print(isPlaying ? 'Playing' : 'Paused');
});
player.nowPlayingStream.listen((NowPlayingInfo info) {
print('Now playing: ${info.title}');
});
The nowPlayingStream automatically extracts ICY metadata from Shoutcast/Icecast streams. If the stream provides metadata (e.g., artist and song title), it will appear here without any extra configuration.
player.volumeStream.listen((VolumeInfo vol) {
print('Volume: ${vol.volume}, Muted: ${vol.isMuted}');
});
A complete player widget with play/pause, skip controls, metadata display, and volume slider:
import 'package:flutter/material.dart';
import 'package:flutter_radio_player/flutter_radio_player.dart';
class RadioPlayerWidget extends StatefulWidget {
const RadioPlayerWidget({super.key});
@override
State<RadioPlayerWidget> createState() => _RadioPlayerWidgetState();
}
class _RadioPlayerWidgetState extends State<RadioPlayerWidget> {
final _player = FlutterRadioPlayer();
double _volume = 0.5;
@override
void initState() {
super.initState();
_player.initialize([
const RadioSource(
url: 'https://s2-webradio.antenne.de/chillout?icy=https',
title: 'Antenne Chillout',
),
const RadioSource(
url: 'https://radio.lotustechnologieslk.net:2020/stream/sunfmgarden?icy=https',
title: 'SunFM - Sri Lanka',
),
], playWhenReady: true);
}
@override
void dispose() {
_player.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Now playing info
StreamBuilder<NowPlayingInfo>(
stream: _player.nowPlayingStream,
builder: (context, snapshot) {
final title = snapshot.data?.title ?? 'No track info';
return Text(title, style: Theme.of(context).textTheme.titleLarge);
},
),
const SizedBox(height: 24),
// Transport controls
StreamBuilder<bool>(
stream: _player.isPlayingStream,
builder: (context, snapshot) {
final isPlaying = snapshot.data ?? false;
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
iconSize: 36,
onPressed: _player.previousSource,
icon: const Icon(Icons.skip_previous_rounded),
),
IconButton(
iconSize: 48,
onPressed: () => isPlaying ? _player.pause() : _player.play(),
icon: Icon(isPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded),
),
IconButton(
iconSize: 36,
onPressed: _player.nextSource,
icon: const Icon(Icons.skip_next_rounded),
),
],
);
},
),
const SizedBox(height: 16),
// Volume slider
Row(
children: [
const Icon(Icons.volume_down_rounded),
Expanded(
child: Slider(
value: _volume,
onChanged: (value) {
setState(() => _volume = value);
_player.setVolume(value);
},
),
),
const Icon(Icons.volume_up_rounded),
],
),
],
);
}
}
See the example app for the full runnable project.
Add the following permissions to your AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
FOREGROUND_SERVICEandFOREGROUND_SERVICE_MEDIA_PLAYBACKare already declared by the plugin.INTERNETmust be declared by the host app.
If your radio streams use plain HTTP (not HTTPS), opt in to cleartext traffic. Either add the attribute to your <application> tag:
<application
android:usesCleartextTraffic="true"
...>
Or, preferred, whitelist only the streaming domains via a network security config (res/xml/network_security_config.xml):
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">your-stream-host.example.com</domain>
</domain-config>
</network-security-config>
Then reference it from your <application> tag: android:networkSecurityConfig="@xml/network_security_config".
Xcode Configuration
Info.plist:<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
| Method | Description |
|---|---|
initialize(sources) |
Set sources and optionally auto-play |
play() |
Resume playback |
pause() |
Pause playback |
playOrPause() |
Toggle play/pause |
setVolume(double) |
Set volume (0.0 to 1.0) |
getVolume() |
Get current volume |
nextSource() |
Skip to next source |
previousSource() |
Skip to previous source |
jumpToSourceAtIndex(i) |
Jump to source at index |
dispose() |
Release player resources |
| Stream | Type | Description |
|---|---|---|
isPlayingStream |
Stream<bool> |
Playback state changes |
nowPlayingStream |
Stream<NowPlayingInfo> |
Track metadata updates |
volumeStream |
Stream<VolumeInfo> |
Volume and mute changes |
| Model | Fields |
|---|---|
RadioSource |
url (String), title (String?), artwork (String?) |
NowPlayingInfo |
title (String?) |
VolumeInfo |
volume (double), isMuted (bool) |
This is a federated plugin split into four packages:
| Package | Description |
|---|---|
flutter_radio_player |
App-facing API |
flutter_radio_player_platform_interface |
Shared interface and models |
flutter_radio_player_android |
Android implementation (Media3/ExoPlayer) |
flutter_radio_player_ios |
iOS implementation (AVFoundation) |
Platform communication uses Pigeon for type-safe code generation.
See the Migration Guide for detailed upgrade instructions.
If you find this plugin useful:
TNuTkL1ZJGu2xntmtzHzSiH5YdVqUeAujrContributions are welcome. Please open an issue first to discuss what you would like to change.