Quran Audio
A Flutter package for Quran audio playback — logic only, no UI. It supports two independent playback systems (full-surah and per-ayah), system media notifications, offline downloads, and repeat modes — all through a single unified QuranAudio facade.
Table of Contents
- Getting started
- Usage Example
- Reciters
- Notification Icon
- Offline Downloads
- Repeat
- Reading State (GetX)
- Metadata Helpers
- How it Works
- Sources
- License
Getting started
Android
The required permissions for audio playback (WAKE_LOCK, and FOREGROUND_SERVICE_MEDIA_PLAYOUT) are automatically added by the package. You don't need to manually edit your AndroidManifest.xml.
Additionally, to enable system-integrated audio controls (notification/lockscreen) using audio_service, your app's MainActivity must extend AudioServiceActivity:
Kotlin:
import com.ryanheise.audioservice.AudioServiceActivity
class MainActivity: AudioServiceActivity()
Java:
import com.ryanheise.audioservice.AudioServiceActivity;
public class MainActivity extends AudioServiceActivity {}
If you don't apply this change, the audio will still work locally, but system controls won't be available.
iOS
For background audio playback, you must add the following to your app's Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
macOS
macOS requires a network entitlement. Open macos/Runner/DebugProfile.entitlements and add:
<key>com.apple.security.network.client</key>
<true/>
⚠️ Also add the same key to
macos/Runner/Release.entitlements, otherwise release builds cannot reach the network for audio streaming.
The library works out of the box on macOS (just_audio uses the native AVAudioPlayer backend). The minimum deployment target should be macOS 11.0.
Windows & Linux
No manual setup is required. The library automatically initializes the media_kit audio backend on Windows and Linux via just_audio_media_kit during QuranAudio.init(). Just run:
flutter run -d windows # or -d linux
Note: System media notifications (lock-screen controls) are not available on Windows, Linux, or macOS — this is an
audio_servicelimitation. Audio playback itself works fully on all desktop platforms.
Web
The library works on web in streaming-only mode (no offline downloads, since browsers have no file system). QuranAudio.init() handles everything automatically:
flutter run -d chrome
- ✅ Play surahs & ayahs (streamed from network URLs)
- ✅ Reciter selection, repeat modes, playback controls
- ❌ Offline downloads (not possible on web — use streaming instead)
- ❌ System media notifications (not supported on web by
audio_service)
Installation
In the pubspec.yaml of your flutter project, add the following dependency:
dependencies:
...
quran_audio: ^1.0.0
Import it:
import 'package:quran_audio/quran_audio.dart';
Initialize it:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize the entire library in one step
await QuranAudio.init();
runApp(const MyApp());
}
💡
QuranAudio.init()accepts optional parameters:await QuranAudio.init( enableSystemNotifications: true, // false = no notifications (lighter, for web/tests) androidNotificationChannelId: 'com.myapp.quran', androidNotificationChannelName: 'Quran Playback', notificationIconSource: 'assets/images/logo.png', // custom icon (asset/url/file) );
By default,
QuranAudio.init()uses the library's bundled icon for the system media notification. To change it at runtime, see Notification Icon.
Notification Icon
The system media notification (lock screen / Bluetooth / car audio) shows an icon. By default the library's bundled logo is used. You can set a custom icon from an asset, a network URL, or a local file:
// From an asset (most common)
await QuranAudio.setNotificationIconFromAsset('assets/images/my_logo.png');
// From a network URL
await QuranAudio.setNotificationIconFromUrl('https://example.com/logo.png');
// From a local file path
await QuranAudio.setNotificationIconFromFile('/path/to/logo.png');
// Auto-detect type (asset / url / file)
await QuranAudio.setNotificationIcon('assets/images/my_logo.png');
// Revert to the library's default icon
await QuranAudio.useDefaultNotificationIcon();
// Read the current icon URI
final Uri? icon = QuranAudio.notificationIcon;
The icon is refreshed live — if playback is active when you call these methods, the notification updates immediately.
Usage Example
Play a Surah
Play a full surah (single MP3 file per surah):
// Play Surah Al-Baqarah
QuranAudio.playSurah(2);
// Play Surah Al-Fatihah
QuranAudio.playSurah(1);
Play a Specific Ayah
Play a specific ayah by surah number and ayah number:
// Play Ayat al-Kursi (2:255), continue to following ayahs
QuranAudio.playAyah(
surah: 2,
ayah: 255,
singleAyah: false, // false = continue to next ayahs
);
// Play a single ayah then stop
QuranAudio.playAyah(
surah: 36,
ayah: 1,
singleAyah: true,
);
Playback Controls
Controls automatically execute on the currently active system — no need to know which is running.
QuranAudio.pause(); // Pause (active system)
QuranAudio.resume(); // Resume (active system)
QuranAudio.stop(); // Stop completely
QuranAudio.next(); // Next (surah or ayah)
QuranAudio.previous(); // Previous (surah or ayah)
QuranAudio.seek(Duration(minutes: 5)); // Seek to position
QuranAudio.togglePlayPause(); // Toggle play/pause
Complete Audio Example
class AudioPlayerScreen extends StatelessWidget {
const AudioPlayerScreen({super.key});
@override
Widget build(BuildContext context) {
final c = QuranAudio.surahController;
return Scaffold(
appBar: AppBar(title: const Text('Quran Audio Player')),
body: Column(
children: [
// Reactive now-playing display
Obx(() {
final surahNo = c.currentSurahNumber.value;
final meta = QuranAudio.surah(surahNo);
return Text('${meta.name} — ${meta.englishName}');
}),
// Control buttons
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: QuranAudio.previous,
icon: const Icon(Icons.skip_previous),
),
IconButton(
onPressed: QuranAudio.togglePlayPause,
icon: const Icon(Icons.play_arrow),
),
IconButton(
onPressed: QuranAudio.next,
icon: const Icon(Icons.skip_next),
),
IconButton(
onPressed: QuranAudio.stop,
icon: const Icon(Icons.stop),
),
],
),
// Progress bar
StreamBuilder<PositionData>(
stream: QuranAudio.positionDataStream,
builder: (context, snap) {
final d = snap.data;
return Slider(
value: d?.position.inSeconds.toDouble() ?? 0,
max: d?.duration.inSeconds.toDouble() ?? 1,
onChanged: (v) => QuranAudio.seek(
Duration(seconds: v.toInt()),
),
);
},
),
// Play buttons
ElevatedButton(
onPressed: () => QuranAudio.playSurah(1),
child: const Text('Play Al-Fatihah'),
),
ElevatedButton(
onPressed: () => QuranAudio.playAyah(surah: 2, ayah: 255),
child: const Text('Play Ayat al-Kursi'),
),
],
),
);
}
}
Reciters
The library ships with two independent reciter lists (matching the original quran_library):
- Surah reciters: 21 reciters (quranicaudio.com, mp3quran.net, tarteel.ai)
- Ayah reciters: 12 reciters (cdn.islamic.network, everyayah.com)
// Change reciter by index
QuranAudio.setSurahReader(2);
QuranAudio.setAyahReader(1);
// Access the lists
final surahReaders = QuranAudio.surahReaders;
final ayahReaders = QuranAudio.ayahReaders;
final currentIndex = QuranAudio.surahReaderIndex;
Custom Reciters
// Override the default surah reciters list
SurahReaders.customReaders = [
const ReaderInfo(
index: 0,
name: 'My Custom Reciter',
readerNamePath: 'my_reader/',
url: 'https://my-server.com/quran/',
),
];
// Override the default ayah reciters list
AyahReaders.customReaders = [
const ReaderInfo(
index: 0,
name: 'My Custom Reciter',
readerNamePath: 'my_reader',
url: 'https://everyayah.com/data/',
),
];
Offline Downloads
// Surah system (single MP3 file)
await QuranAudio.downloadSurah(2); // Download Al-Baqarah
final ready = await QuranAudio.isSurahDownloaded(2);
await QuranAudio.deleteSurahDownload(2);
// Ayah system (one MP3 per ayah)
await QuranAudio.downloadSurahAyahs(2); // All ayahs of Al-Baqarah
await QuranAudio.downloadAyahRange(2, from: 1, to: 10); // Range
final ready = await QuranAudio.isSurahAyahsDownloaded(2);
await QuranAudio.deleteSurahAyahDownloads(2);
// Cancel any ongoing download
QuranAudio.cancelDownload();
Repeat
QuranAudio.repeatAyah(); // Repeat the current ayah
QuranAudio.repeatSurah(); // Repeat the current surah
QuranAudio.repeatAyahRange( // Repeat a range of ayahs
fromAyah: 1,
toAyah: 10,
times: 3,
);
QuranAudio.disableRepeat(); // Disable repeat
Reading State (GetX)
⚠️ Important: Each
Obxmust read.valuedirectly from a controller inside its builder. Do not wrap a whole widget inObxif Rx values are read in a deeperbuild— GetX will throw an error.
final c = QuranAudio.surahController;
// ✅ Correct — .value is read directly inside Obx
Obx(() => Text(
c.isPlaying.value ? 'Playing' : 'Paused',
));
Obx(() => Text('Surah: ${c.currentSurahNumber.value}'));
Progress bar & reactive card
// Progress bar via StreamBuilder (no Obx needed)
StreamBuilder<PositionData>(
stream: QuranAudio.positionDataStream,
builder: (context, snap) {
final d = snap.data;
return Slider(
value: d?.position.inSeconds.toDouble() ?? 0,
max: d?.duration.inSeconds.toDouble() ?? 1,
onChanged: (v) => QuranAudio.seek(Duration(seconds: v.toInt())),
);
},
);
// A reactive now-playing card
class NowPlayingCard extends StatelessWidget {
Widget build(BuildContext context) {
final surahC = QuranAudio.surahController;
final ayahC = QuranAudio.ayahController;
return Obx(() {
final surahNo = surahC.currentSurahNumber.value;
final ayahNo = ayahC.currentAyahInSurah.value;
final playing = surahC.isPlaying.value || ayahC.isPlaying.value;
final meta = QuranAudio.surah(surahNo);
return Card(
child: ListTile(
title: Text(meta.name),
subtitle: Text(playing ? 'Playing' : 'Paused'),
trailing: Text('$ayahNo / ${meta.ayahCount}'),
),
);
});
}
}
Metadata Helpers
Instead of bundling the full Quran file (745KB), the library uses a tiny (~3KB) JSON holding only surah numbers, names, and ayah counts. Unique ayah numbers (UQ 1..6236) are computed cumulatively — no need to store them.
QuranAudio.ayahCountOf(2); // → 286
QuranAudio.uqNumberOf(2, 255); // → 262
QuranAudio.surah(2).name; // → "Surat Al-Baqarah"
QuranAudio.allSurahs; // → List<SurahMeta> (114 entries)
// Validation
QuranAudio.metadata.isValidSurah(2); // → true
QuranAudio.metadata.isValidAyah(2, 255); // → true
How it Works
The library maintains two independent playback systems, each with its own reciters, URLs, and mechanisms:
| System | Description | Reciters | Sources |
|---|---|---|---|
Surah (SurahAudioController) |
One MP3 per full surah | 21 | quranicaudio.com, mp3quran.net, tarteel.ai |
Ayah (AyahAudioController) |
One MP3 per ayah | 12 | cdn.islamic.network, everyayah.com |
They share a single AudioPlayer via a coordination layer (AudioEngine). When one system takes control, the other is automatically stopped and its state is reset.
- Auto-advance: Ayahs play sequentially (playlist + windowing); surahs auto-advance to the next (1→114) on completion.
- Smart controls:
pause()/resume()/next()/previous()execute on whichever system is currently active. - Direct access: For advanced use, the controllers are exposed via
QuranAudio.surahControllerandQuranAudio.ayahController.
Sources
- Quran metadata (surah names & ayah counts): derived from King Fahd Glorious Quran Printing Complex data
- Audio sources: quranicaudio.com, mp3quran.net, tarteel.ai, cdn.islamic.network, everyayah.com
License
MIT for code. Read more about the license here.
Libraries
- quran_audio
- مكتبة تشغيل صوتي للقرآن الكريم - لوجيك فقط بدون UI.