Quran Library
Choose your language for the documentation:
Important note before starting to use: Please make:
useMaterial3: false,
In order not to cause any formation problems
Table of Contents
- Getting started
- Usage Example
- Utils
- Fonts Download
- Word Audio (Word-by-Word)
- Tafsir
- Audio Playback
- AI Recitation Checking (التسميع)
- Sources
- License
Getting started
Android
The required permissions for audio playback (WAKE_LOCK, and FOREGROUND_SERVICE_MEDIA_PLAYBACK) 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 AudioService.init() may fail and 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>
This allows audio playback to continue when the app is in the background.
In the pubspec.yaml of your flutter project, add the following dependency:
dependencies:
...
quran_library: ^5.0.0
Import it:
import 'package:quran_library/quran_library.dart';
Initialize it:
Future<void> main() async {
await WidgetsFlutterBinding.ensureInitialized();
await QuranLibrary.init();
runApp(
const MyApp(),
);
}
Usage Example
Basic Quran Screen
/// You can just add it to your code like this:
class MyQuranPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return QuranLibraryScreen(
parentContext: context, // Required
);
}
}
or give it some options:
QuranLibraryScreen(
parentContext: context,
withPageView: true,
useDefaultAppBar: true,
isShowAudioSlider: true,
showAyahBookmarkedIcon: false,
isDark: isDark,
appLanguageCode: Get.locale!.languageCode,
backgroundColor: context.theme.colorScheme.surface,
textColor: context.textDarkColor,
ayahSelectedBackgroundColor:
context.theme.colorScheme.primary.withValues(alpha: .2),
ayahIconColor: context.theme.colorScheme.primary,
surahInfoStyle:
SurahInfoStyle.defaults(isDark: isDark, context: context)
.copyWith(
ayahCount: 'aya_count'.tr,
firstTabText: 'surahNames'.tr,
secondTabText: 'aboutSurah'.tr,
bottomSheetWidth: 500,
),
basmalaStyle: BasmalaStyle(
verticalPadding: 0.0,
basmalaColor: context.textDarkColor.withValues(alpha: .8),
basmalaFontSize: isLoadedFont ? 120.0 : 25.0,
),
ayahStyle: AyahAudioStyle.defaults(isDark: isDark, context: context)
.copyWith(
dialogWidth: 300,
readersTabText: 'readers'.tr,
),
topBarStyle:
QuranTopBarStyle.defaults(isDark: isDark, context: context)
.copyWith(
showAudioButton: false,
showFontsButton: false,
tabIndexLabel: 'index'.tr,
tabBookmarksLabel: 'bookmarks'.tr,
tabSearchLabel: 'search'.tr,
),
indexTabStyle:
IndexTabStyle.defaults(isDark: isDark, context: context)
.copyWith(
tabSurahsLabel: 'surahs'.tr,
tabJozzLabel: 'juzz'.tr,
),
searchTabStyle:
SearchTabStyle.defaults(isDark: isDark, context: context)
.copyWith(
searchHintText: 'search'.tr,
),
bookmarksTabStyle:
BookmarksTabStyle.defaults(isDark: isDark, context: context)
.copyWith(
emptyStateText: 'no_bookmarks_yet'.tr,
greenGroupText: 'greenBookmarks'.tr,
yellowGroupText: 'yellowBookmarks'.tr,
redGroupText: 'redBookmarks'.tr,
),
ayahMenuStyle:
AyahMenuStyle.defaults(isDark: isDark, context: context)
.copyWith(
copySuccessMessage: 'ayah_copied'.tr,
showPlayAllButton: false,
),
tafsirStyle:
TafsirStyle.defaults(isDark: isDark, context: context).copyWith(
widthOfBottomSheet: 500,
heightOfBottomSheet: MediaQuery.sizeOf(context).height * 0.9,
changeTafsirDialogHeight: MediaQuery.sizeOf(context).height * 0.9,
changeTafsirDialogWidth: 400,
tafsirNameWidget: customSvgWithCustomColor(
'assets/svg/tafseer_white.svg',
color: context.theme.colorScheme.primary,
height: 24,
),
tafsirName: 'tafsir'.tr,
translateName: 'translate'.tr,
tafsirIsEmptyNote: 'tafsirIsEmptyNote'.tr,
footnotesName: 'footnotes'.tr,
),
topBottomQuranStyle: TopBottomQuranStyle.defaults(
isDark: isDark,
context: context,
).copyWith(
hizbName: 'hizb'.tr,
juzName: 'juz'.tr,
sajdaName: 'sajda'.tr,
),
),
Individual Surah Display
Expand section
/// For displaying a single surah with custom pagination
SurahDisplayScreen(
/// [surahNumber] The surah number to display
surahNumber: 1, // For Al-Fatihah
/// [onPageChanged] if provided it will be called when a surah page changed
onPageChanged: (int pageIndex) => print("Surah page changed: $pageIndex"),
/// [isDark] enable or disable dark mode
isDark: false,
/// [basmalaStyle] Change the style of Basmala
basmalaStyle: BasmalaStyle.defaults(
isDark: isDark,
context: context,
).copyWith(
basmalaColor: Colors.black,
basmalaWidth: 160.0,
basmalaHeight: 30.0,
),
/// [bannerStyle] Change the style of banner
bannerStyle: BannerStyle.defaults(
isDark: isDark,
context: context,
).copyWith(
isImage: false,
bannerSvgHeight: 40.0,
bannerSvgWidth: 150.0,
),
/// and more options...
),
Single Ayah Display
Expand section
/// For displaying a single ayah from any surah
GetSingleAyah(
/// [surahNumber] - must be between 1 and 114
surahNumber: 1, // Surah number
/// [ayahNumber] - ayah number within the surah
ayahNumber: 2, // Ayah number
/// [textColor] - optional text color
textColor: Colors.black,
/// [isDark] - optional, default is false
isDark: false,
/// [fontSize] - optional, default is 22
fontSize: 24.0,
/// [isBold] - optional, default is true
isBold: true,
),
Partial Pages (single or range) + Highlighting
Expand section
You can display just one specific page or a range of pages using QuranPagesScreen.
Requirements:
- Pass the parent widget context via
parentContext. - Choose either a single
pageor a range usingstartPageandendPage.
Basic examples:
// Single page
QuranPagesScreen(
parentContext: context,
page: 6,
)
// Range of pages (inclusive)
QuranPagesScreen(
parentContext: context,
startPage: 6,
endPage: 11,
)
// Optional: disable page view if you want a static view
QuranPagesScreen(
parentContext: context,
startPage: 6,
endPage: 7,
withPageView: false,
)
Programmatic highlighting (by surah/ayah numbers):
// Highlight by Surah and Ayah numbers
QuranPagesScreen(
parentContext: context,
page: 6,
highlightedAyahNumbersBySurah: {
18: [1, 5, 10], // Surah Al-Kahf: ayahs 1,5,10
36: [3], // Surah Ya-Sin: ayah 3
},
)
// Highlight by page range + ayah numbers (within those pages)
QuranPagesScreen(
parentContext: context,
startPage: 6,
endPage: 11,
highlightedAyahNumbersInPages: [
(start: 6, end: 11, ayahs: [1, 3, 5]),
],
)
// If you already have unique ayah numbers (UQ), you can still pass them directly
QuranPagesScreen(
parentContext: context,
page: 6,
highlightedAyahs: [1023, 1024, 1025],
)
Optional multi-select mode (keeps multiple ayahs selected on long-press):
QuranPagesScreen(
parentContext: context,
page: 6,
enableMultiSelect: true,
)
Notes:
QuranPagesScreenis a StatelessWidget.- Highlighting is applied programmatically and does not replace manual selection.
- You can combine multiple highlighting inputs; they’ll be merged internally.
Using GetSingleAyah in a list:
Expand section
class SingleAyahExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Display Single Ayahs')),
body: ListView(
padding: EdgeInsets.all(16),
children: [
// Ayat al-Kursi
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('Ayat al-Kursi', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 10),
GetSingleAyah(
surahNumber: 2, // Al-Baqarah
ayahNumber: 255, // Ayat al-Kursi
fontSize: 20,
textColor: Colors.brown,
),
],
),
),
),
SizedBox(height: 16),
// Complete Al-Fatihah
Card(
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Surah Al-Fatihah', style: TextStyle(fontWeight: FontWeight.bold)),
SizedBox(height: 10),
// Display all ayahs of Al-Fatihah
...List.generate(7, (index) => Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: GetSingleAyah(
surahNumber: 1,
ayahNumber: index + 1,
fontSize: 18,
isDark: false,
),
)),
],
),
),
),
],
),
);
}
}
Utils
The package provides a lot of utils like:
-
Getting all Quran's Jozzs, Hizbs, and Surahs
final jozzs = QuranLibrary.allJoz;
final hizbs = QuranLibrary.allHizb;
final surahs = QuranLibrary.getAllSurahs();
final ayahsOnPage = QuranLibrary().getAyahsByPage();
/// [getSurahInfo] let's you get a Surah with all its data when you pass Surah number
final surah = QuranLibrary().getSurahInfo(1);
-
to jump between pages, Surahs or Hizbs you can use:
/// [jumpToAyah] let's you navigate to any ayah..
/// It's better to call this method while Quran screen is displayed
/// and if it's called and the Quran screen is not displayed, the next time you
/// open quran screen it will start from this ayah's page
QuranLibrary().jumpToAyah(AyahModel ayah);
/// or you can use:
/// jumpToPage, jumpToJoz, jumpToHizb, jumpToBookmark and jumpToSurah.
-
Adding, setting, removing, getting and navigating to bookmarks:
// In init function
QuranLibrary().init(userBookmarks: [Bookmark(id: 0, colorCode: Colors.red.value, name: "Red Bookmark")]);
final usedBookmarks = QuranLibrary().getUsedBookmarks();
QuranLibrary().setBookmark(surahName: 'Al-Fatihah', ayahNumber: 5, ayahId: 5, page: 1, bookmarkId: 0);
QuranLibrary().removeBookmark(bookmarkId: 0);
QuranLibrary().jumpToBookmark(BookmarkModel bookmark);

-
searching for any Ayah
TextField(
onChanged: (txt) {
final _ayahs = QuranLibrary().search(txt);
setState(() {
ayahs = [..._ayahs];
});
},
decoration: InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.black),),
hintText: 'Search',
),
),
-
Word Info (Recitations / Tasreef / Eerab)
/// Open Word Info bottom sheet (with on-demand download)
await QuranLibrary().showWordInfoByNumbers(
context: context,
surahNumber: 1,
ayahNumber: 1,
wordNumber: 1,
initialKind: WordInfoKind.recitations,
isDark: true,
);
/// (Optional) download a specific kind programmatically
if (!QuranLibrary().isWordInfoKindDownloaded(WordInfoKind.recitations)) {
await QuranLibrary().downloadWordInfoKind(kind: WordInfoKind.recitations);
}
Word Audio (Word-by-Word)
Enable word audio playback to hear individual words or all words of an ayah sequentially.
// Initialize word audio (call once after QuranLibrary.init())
QuranLibrary.initWordAudio();
-
Play a Single Word
// Play word audio using WordRef
await QuranLibrary().playWordAudio(
ref: const WordRef(surahNumber: 1, ayahNumber: 1, wordNumber: 1),
);
// Or using numbers directly
await QuranLibrary().playWordAudioByNumbers(
surahNumber: 1,
ayahNumber: 1,
wordNumber: 1,
);
-
Play All Words of an Ayah
// Play all words of an ayah sequentially
await QuranLibrary().playAyahWordsAudioByNumbers(
surahNumber: 1,
ayahNumber: 1,
);
-
Stop & State
// Stop playback
await QuranLibrary().stopWordAudio();
// Check state
bool isPlaying = QuranLibrary().isWordAudioPlaying;
bool isLoading = QuranLibrary().isWordAudioLoading;
bool isAyahMode = QuranLibrary().isPlayingAyahWords;
int wordCount = QuranLibrary().getAyahWordCount(surahNumber: 1, ayahNumber: 1);
Note: Audio buttons also appear automatically inside the Word Info bottom sheet when word audio is initialized.
Fonts Download
To download Quran fonts, you have two options:
-
As for using the default dialog, you can modify the style in it.
-
Or you can create your own design using all the functions for downloading fonts.
macOS needs you to request a specific entitlement in order to access the network.
To do that: open macos/Runner/DebugProfile.entitlements and add the following key-value pair.
<key>com.apple.security.network.client</key>
<true/>
///
/// to get the fonts download dialog just call [getFontsDownloadDialog]
///
/// and pass the language code to translate the number if you want,
/// the default language code is 'ar' [languageCode]
/// and style [DownloadFontsDialogStyle] is optional.
QuranLibrary().getFontsDownloadDialog(downloadFontsDialogStyle, languageCode);
/// to get the fonts download widget just call [getFontsDownloadWidget]
Widget getFontsDownloadWidget(context, {downloadFontsDialogStyle, languageCode});
/// to get the fonts download method just call [fontsDownloadMethod]
QuranLibrary().fontsDownloadMethod;
Tafsir
-
Usage Example
// get current list
final all = TafsirController.instance.items; // includes defaults + customs
// add a custom sql file (File is from file picker)
final added = await TafsirController.instance.addCustomFromFile(
sourceFile: pickedFile,
displayName: 'My Custom Tafsir',
bookName: 'My Book',
type: TafsirFileType.json,
);
/// Show a popup menu to change the tafsir style.
QuranLibrary().changeTafsirPopupMenu(TafsirStyle tafsirStyle, {int? pageNumber});
/// Fetch tafsir for a specific page by its page number.
QuranLibrary().fetchTafsir({required int pageNumber});
/// Check if the tafsir is already downloaded.
QuranLibrary().getTafsirDownloaded(int index);
/// Get the list of tafsir and translation names.
QuranLibrary().tafsirAndTraslationCollection;
/// Change the selected tafsir when the switch button is pressed.
QuranLibrary().changeTafsirSwitch(int index, {int? pageNumber});
/// Get the list of available tafsir data.
QuranLibrary().tafsirList;
/// Get the list of available translations.
QuranLibrary().translationList;
/// Fetch translations from the source.
QuranLibrary().fetchTranslation();
/// Download the tafsir by the given index.
QuranLibrary().tafsirDownload(int i);
/// (Optional) Download Tajweed (ayah-level) data used inside the Tafsir bottom sheet
if (!QuranLibrary().isTajweedAyahDownloaded) {
await QuranLibrary().downloadTajweedAyah();
}
Audio Playback
This section provides comprehensive capabilities for audio playback of the Holy Quran with background playback support and advanced audio file management.
-
Verse Audio Playback
/// Play a verse or group of verses starting from a specific verse
await QuranLibrary().playAyah(
context: context,
currentAyahUniqueNumber: 1, // Unique ayah number
playSingleAyah: true, // true for single ayah, false to continue
);
/// Move to next verse and play it
await QuranLibrary().seekNextAyah(
context: context,
currentAyahUniqueNumber: 5,
);
/// Move to previous verse and play it
await QuranLibrary().seekPreviousAyah(
context: context,
currentAyahUniqueNumber: 10,
);
-
Surah Audio Playback
/// Play a complete surah from beginning to end
await QuranLibrary().playSurah(surahNumber: 1); // Al-Fatihah
await QuranLibrary().playSurah(surahNumber: 2); // Al-Baqarah
/// Move to next surah and play it
await QuranLibrary().seekToNextSurah();
/// Move to previous surah and play it
await QuranLibrary().seekToPreviousSurah();
-
Download Management
/// Start downloading a surah for offline playback
await QuranLibrary().startDownloadSurah(surahNumber: 1);
/// Cancel ongoing download
QuranLibrary().cancelDownloadSurah();
-
Position Control & Resume
/// Get current/last surah number
int currentSurah = QuranLibrary().currentAndLastSurahNumber;
/// Get last position as formatted text (like "05:23")
String lastTimeText = QuranLibrary().formatLastPositionToTime;
/// Get last position as Duration object for programming operations
Duration lastDuration = QuranLibrary().formatLastPositionToDuration;
/// Play from the last position where user stopped
await QuranLibrary().playLastPosition();
-
Complete Audio Example
class AudioControlExample extends StatefulWidget {
@override
_AudioControlExampleState createState() => _AudioControlExampleState();
}
class _AudioControlExampleState extends State<AudioControlExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Quran Audio Player')),
body: Column(
children: [
// Display current surah
Text('Current Surah: ${QuranLibrary().currentAndLastSurahNumber}'),
// Display last position
Text('Last Position: ${QuranLibrary().formatLastPositionToTime}'),
// Control buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Play from last position
ElevatedButton(
onPressed: () => QuranLibrary().playLastPosition(),
child: Text('Resume from where you left'),
),
// Play Al-Fatihah
ElevatedButton(
onPressed: () => QuranLibrary().playSurah(surahNumber: 1),
child: Text('Surah Al-Fatihah'),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Previous surah
IconButton(
onPressed: () => QuranLibrary().seekToPreviousSurah(),
icon: Icon(Icons.skip_previous),
),
// Previous ayah
IconButton(
onPressed: () => QuranLibrary().seekPreviousAyah(
context: context,
currentAyahUniqueNumber: 10,
),
icon: Icon(Icons.fast_rewind),
),
// Next ayah
IconButton(
onPressed: () => QuranLibrary().seekNextAyah(
context: context,
currentAyahUniqueNumber: 5,
),
icon: Icon(Icons.fast_forward),
),
// Next surah
IconButton(
onPressed: () => QuranLibrary().seekToNextSurah(),
icon: Icon(Icons.skip_next),
),
],
),
// Download buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => QuranLibrary().startDownloadSurah(surahNumber: 2),
child: Text('Download Surah Al-Baqarah'),
),
ElevatedButton(
onPressed: () => QuranLibrary().cancelDownloadSurah(),
child: Text('Cancel Download'),
),
],
),
],
),
);
}
}
-
You can also use the default Quran font or Naskh font
/// [hafsStyle] is the default style for Quran so all special characters will be rendered correctly
QuranLibrary().hafsStyle;
/// [naskhStyle] is the default style for other text.
QuranLibrary().naskhStyle;
AI Recitation Checking (التسميع)
AI-powered memorization checking: toggle tasmee mode from the Quran top bar (mic button) — the page's words are hidden (ayah-end numbers stay visible), audio and other controls step aside, and a record/stop bar takes over. While you recite, words are revealed progressively: the current word is highlighted, and each completed word is colored green (correct) or red (incorrect) after it is fully pronounced. Stopping opens a results bottom sheet listing tajweed / pronunciation / tashkeel errors with expected vs recited phonemes.
Two engines are supported:
| Engine | Live word reveal | Internet |
|---|---|---|
Offline (default) — Quran-Lab zipformer v3.1 via sherpa_onnx |
✅ | Only once — a 73MB model is downloaded at first use (never bundled in assets), then tasmee works fully offline |
| Server — quran-muaalem | ❌ (results after stop) | Required |
Engine choice, server URL, and model download are managed from the in-mode settings sheet (⚙) and persisted automatically. Server mode is batch-only by nature: words appear with the results after stopping.
Error-type underlines: each revealed word gets a colored underline drawn by
the text engine (works with the COLR mushaf fonts): green for correct words,
and — while reciting — a live-classified color for mistakes: purple for
tajweed (madd/shadda/qalqalah/ghunnah/ikhfaa), red for pronunciation
(wrong/extra/missing letter), orange for tashkeel. The final evaluation
after stopping is authoritative and refines the classification. All colors are
themeable via TasmeeStyle.correctColor, tajweedErrorColor,
normalErrorColor, and tashkeelErrorColor.
Note: automatic correction can be wrong and does not replace a certified teacher — accuracy is lower for children under 12. (Required by the Quran-Lab NPL-1.2 model license.)
Programmatic Control (TasmeeCtrl)
Everything the tasmee UI does is available programmatically through
TasmeeCtrl.instance:
-
Toggle Tasmee Mode
final tasmee = TasmeeCtrl.instance;
// Enter tasmee mode (hides the current page's words, stops audio/auto-scroll)
await tasmee.enterTasmeeMode();
// Or toggle (enter if idle, exit if active)
tasmee.toggleTasmeeMode();
// Exit and restore the normal view
tasmee.exitTasmeeMode();
-
Record & Evaluate
// Start recording (prepares the engine — downloads the model on first
// offline use — then streams live word tracking)
await tasmee.startRecording();
// Stop, evaluate, and open the results bottom sheet
await tasmee.stopRecording();
// Re-hide everything and start over on the same page
await tasmee.retryTasmee();
-
Peek at the Page & State
// Temporarily show all words while staying in tasmee mode (the eye button)
tasmee.toggleShowAllWords();
// Read the reactive state (usable inside Obx)
final s = tasmee.state;
bool active = s.isTasmeeMode.value; // tasmee mode on?
bool recording = tasmee.isRecording; // recording now?
bool processing = tasmee.isProcessing; // evaluating after stop?
int done = s.completedWords.value; // completed words so far
int total = s.totalWords.value; // words on the page
RecitationResult? result = s.lastResult.value; // last evaluation
String error = s.lastError.value; // last error message
bool modelReady = s.isModelReady.value; // offline model on disk?
double progress = s.modelDownloadProgress.value; // 0.0 – 1.0
-
Word Status Lookup
Each word's tasmee status is keyed by '$ayahUq:$wordNumber' (wordNumber is
1-based, matching WordRef/QpcV4WordSegment):
final status = tasmee.wordStatusOf('$12:3');
// TasmeeWordStatus.hidden | current | correct | incorrect
-
Engine Settings
// Choose the engine (persisted via GetStorage)
tasmee.setEngineMode(TasmeeEngineMode.offline); // zipformer (default)
tasmee.setEngineMode(TasmeeEngineMode.online); // quran-muaalem server
// Server mode: set and verify the server URL
tasmee.setServerUrl('http://localhost:8001');
final ok = await tasmee.testServerConnection();
// Pre-download the offline model ahead of first use
final ready = await TasmeeModelService().isModelReady();
if (!ready) {
await tasmee.downloadModelIfNeeded(); // progress in state.modelDownloadProgress
// or directly:
// await TasmeeModelService().downloadModel(onProgress: (p) => print(p));
}
Low-Level Engine API
For custom integrations (no UI), the ported engine can be used directly:
// ── Offline (default) ─────────────────────────────────────────
await Recitation.initZipformer(); // model auto-resolved/downloaded separately
// ── Or online (quran-muaalem server) ──────────────────────────
Recitation.init(serverUrl: 'http://localhost:8001');
// Health & readiness
final healthy = await Recitation.isEngineHealthy();
final offline = Recitation.isOffline; // true for zipformer
final url = Recitation.serverUrl;
// ── Session: whole current page (what the UI uses) ────────────
final range = TasmeeReferenceStore.instance.buildRange([
(suraIdx: 1, ayaIdx: 1),
(suraIdx: 1, ayaIdx: 2),
]);
final session = Recitation.createSession(range: range);
// Live streaming (offline engine only): word-by-word callbacks
session.onWordDone = (verseIdx, wordIdx, correct) {
// fired when a word is fully pronounced, with its verdict
};
session.onRangeComplete = () => print('page completed');
await session.startLive();
// ... user recites; session.currentVerseIdx / currentWordIdx update live
final result = await session.stopLive();
// ── Session: single ayah (batch — works on both engines) ──────
final s2 = Recitation.createSession(suraIdx: 1, ayaIdx: 1);
await s2.start(); // records WAV then evaluates on stop
final r2 = await s2.stop();
print(r2?.errors); // List<RecitationError>
-
Reading the Result
final result = session.result.value; // RecitationResult?
result?.hasMatch; // was a match found in the Quran?
result?.isFullyCorrect; // no errors at all?
result?.start; // SurahAyahPosition (suraIdx/ayaIdx)
result?.errors; // all RecitationError items
result?.tajweedErrors; // tajweed-only
result?.normalErrors; // wrong-letter pronunciation
result?.tashkeelErrors; // haraka mistakes
result?.predictedPhonemes; // what the user actually recited
// Each error:
for (final e in result?.errors ?? <RecitationError>[]) {
e.description; // ready-to-show Arabic description
e.wordText; // the affected Quranic word (or null)
e.expectedPh; // expected phoneme
e.predictedPh; // recited phoneme
e.suraIdx; // position (offline range mode)
e.ayaIdx;
e.wordIdx; // 0-based word index within the ayah
}
-
Model & Reference Internals
// Model file management (73MB ONNX — runtime download, never bundled)
final model = TasmeeModelService();
await model.isModelReady(); // exists & valid (> 60MB)
await model.downloadModel(onProgress: (p) {});
await model.deleteModel();
print(kZipformerModelUrl); // the GitHub release URL
// Shared phoneme reference (used to build page ranges)
final store = TasmeeReferenceStore.instance;
await store.load(); // loads tokens + full-Quran reference once
final verseText = store.reference?.getReference(suraIdx: 1, ayaIdx: 1)?.uthmani;
final pageRange = store.buildRange([(suraIdx: 1, ayaIdx: 1)]);
// Pure tracker (what powers the live word reveal) — testable standalone
final tracker = RangeLiveTracker(
range: pageRange!,
onWordDone: (v, w, correct) {},
onRangeComplete: () {},
);
UI Surfaces & Theming
// Control bar (replaces the ayah audio bar inside tasmee mode)
TasmeeControlWidget(isDark: false);
// Bottom sheets (also open automatically after each evaluation)
await showTasmeeResultSheet(context: context, isDark: false);
await showTasmeeSettingsSheet(context: context, isDark: false);
// Top bar entry point
QuranTopBarStyle(showTasmeeButton: true, tasmeeIconPath: myMicSvg);
Everything is themable via TasmeeStyle (control bar, results/settings
sheets, every label for i18n, hide colour, verdict colours) injected through
QuranLibraryTheme(tasmeeStyle: ...) or read from TasmeeTheme.of(context).
Permissions required from the host app
The package already merges RECORD_AUDIO into the Android manifest. iOS/macOS
hosts must declare microphone usage themselves:
ios/Runner/Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>يستخدم التطبيق الميكروفون لتسجيل تسميعك والتحقق من قراءتك.</string>
macos/Runner/DebugProfile.entitlements and Release.entitlements:
<key>com.apple.security.device.audio-input</key>
<true/>
Quick Start
// The button lives in the default Quran top bar — nothing else to wire.
QuranLibraryScreen(isDark: false);
// Optional: pre-download the offline model ahead of first use.
final ready = await TasmeeModelService().isModelReady();
if (!ready) {
await TasmeeModelService().downloadModel();
}
The tasmee button is hidden automatically on web — the microphone and the offline model are not supported there.
Sources
-
Quran text and metadata: King Fahd Glorious Quran Printing Complex — Quran Developer Portal
-
Fonts, Tafsir, and Translations: Quranic Universal Library (QUL) by Tarteel
License
MIT for code. QCF fonts are provided via Quranic Universal Library (QUL). Ensure you comply with QUL terms (and any upstream KFGQPC terms) when distributing applications that include or bundle these assets.
Read more about the license here.
For additional terms regarding QCF fonts and QUL resources, see NOTICE.