My Stream - القرآن الكريم

An Android app for listening to the Quran: 146 reciters, each surah streamed or kept on the phone, and a page-image mushaf that reads offline once it is downloaded.

It is built around one rule that every play follows: look on the phone first, and use the network only for what isn't there. A downloaded surah plays in airplane mode; a surah that would need the network says so at once instead of hanging.

The mushaf reader, bookmarks and read-along audio are adapted from the open-source Quran for Android project. The reciter list, the phone-first lookup, the network check and the downloads are this app's own code.

Get it on Google Play

Plays the five stages below in order, about 40 seconds. Scroll or press Esc to stop.

Package
com.medoapps.www.onlinequran
On Google Play since
July 2018
Version
13.0.0, July 2026
Runs on
Android 5.0 and later
Built with
Java and Kotlin, ExoPlayer, SQLDelight
Reciters
146 to stream or download by surah
Mushaf reader
Adapted from Quran for Android
9:41Wi-Fi
Mishary Alafasyafs, 114 surahs
    Nothing playing

    Switch the connection, then tap a surah. Four of these six are on the phone Illustrative input

      A drawn mock, not a screenshot; in the app, tapping a surah opens the player screen. Row states are the real output of the app's own lookup code (stage 2) for an illustrative set of downloads. The rule and the toast text are the app's: MediaPlayerService.kt:357-361.

      1Pick a reciter, get a surah list

      The reciter list ships inside the app, so building it needs no request. Each reciter carries everything needed to make its surah list on the phone.

      • 146 reciters, each with an Arabic and an English name; 10 also name a riwaya folder, 9 Hafs and 1 Warsh (LnaguageClass.java:150-480).
      • Each one maps to one of mp3quran.net's numbered servers by a chain of name rules (serverNumber(), :481-1025).
      • A range table says which surahs a reciter has: a span such as 78 to 114, or a hand-picked list (managment.java:163). A partial reciter only lists what is in its range.

      117 reciters have all 114 surahs. The other 29 range from 113 surahs down to a single one.

      146 reciters, filled by how many surahs each has

      All 114 surahs
      117
      Partial
      29
      Hand-picked lists
      15

      Reciters per mp3quran.net server

      Counts and servers come from running the app's own serverNumber() and autherRanageDetermine(), copied verbatim from the published code, over all 146 reciters offline.
      The 29 partial reciters
      Reciter idServerSurahsList

      2Look on the phone first

      Before anything plays, every surah in the list is resolved to one source, checked in a fixed order: a file in Music/MyStream, then a MediaStore entry, and only then a stream URL. The answer is shown on the row as “From phone” or “online”.

      • The file name is fixed, AhmedHashim_<reciter><surah>.mp3, so each check is a lookup in a set (LnaguageClass.java:1370-1387).
      • One folder listing and one cached MediaStore query serve all 114 rows, warmed up off the main thread when the reciter list opens (RecitesName.java:189). The code's comments put the old cost at up to 114 disk and system calls per list.
      • The MediaStore step exists for reinstalls: the app can no longer read its old files directly, but MediaStore still finds them (:1342).

      Resolving a reciter's list

      On this phone Illustrative inputMusic/MyStream/AhmedHashim_afs001.mp3 …afs018.mp3 …afs036.mp3, and a MediaStore entry for AhmedHashim_afs067.mp3 left from before a reinstall
      1. 1. File in Music/MyStreamexistingFiles.contains(name)0From phone
      2. 2. MediaStore entrydownloadedUris.get(name)0From phone
      3. 3. Stream URLhttps://server8.mp3quran.net/…0online
      FileMediaStoreStream URLNot in this reciter's range
      Every row is the real output of the app's GuranAya() method, copied verbatim from the published code and run offline against a scratch folder. Which files exist is illustrative, and the MediaStore query is replaced by a fixed map.

      3Refuse a stream with no network

      The whole list becomes the play queue, each row carrying the source it resolved to. Right before ExoPlayer prepares a source, the player checks the connection: a URL with no network stops there, with the toast “Network Error”, and the playback service shuts down.

      • The queue is the resolved list as is (SongsManager.getPlayList, NewQuranPlayer.kt:359-370).
      • The check is !isNetworkAvailable && data.contains("http") (MediaPlayerService.kt:357). It only stops URLs.
      • A file path or a content:// URI is prepared without asking (:363-364), which is why a downloaded surah plays in airplane mode.

      Without the check, a stream with no network would leave the player loading, since a playback error is only logged (:279-281). With it, the answer is immediate and says why.

      Three sources at the check

      Al-KahfMusic/MyStream/AhmedHashim_afs018.mp3
      Al-Mulkcontent://media/external/audio/media/1042
      Al-Baqarahhttps://server8.mp3quran.net/afs/002.mp3
      355        val test = SeparateFunctions(applicationContext)
      356        Log.d(TAG, "startMediaSeparatly: network=${test.isNetworkAvailable}")
      357        if (!test.isNetworkAvailable && activeAudio!!.data.contains("http")) {
      358            Toast.makeText(applicationContext, "Network Error", Toast.LENGTH_SHORT).show()
      359            destroyService()
      360            return
      361        }
      362
      363        if (!activeAudio!!.data.contains("http") || test.isNetworkAvailable) {
      364            exoPlayer!!.prepare()  // always async in ExoPlayer — no race condition
      365        }
      Worked from the published code above, not captured on a device: the refusal is an Android toast. The three sources are the stage 2 rows; the toast text is the app's own string.

      ClearedEverything above comes from the phone. Below this line the app goes to the network: mp3quran.net for surah audio, and the mushaf's page hosts.

      4Stream it, or keep it

      Past the check, a URL streams through ExoPlayer in a foreground media service. Any surah can also be downloaded, and the next lookup finds it on the phone.

      • Streaming runs as a foreground media-playback service with notification controls (MediaPlayerService.kt:751). It drops to 10% volume while another app plays a short sound, and pauses when headphones are unplugged or, where phone-state access is granted, a call comes in (:283-310, :500-560).
      • The download queue only takes rows whose source is still a URL (AyaList.java:786-795), and “Select all” picks exactly those (:746-761).
      • On Android 10 and later each file goes in through MediaStore under Music/MyStream, with the same fixed name the lookup checks (DownloadService.java:357-366).
      • When a download finishes, the list reloads and the lookup cache is cleared (AyaList.java:898). The files sit in shared storage, so they outlive a reinstall.

      Download two surahs, then look again

      Download all list
        From phone
        0
        Online
        114
        “Select all” would pick
        114
        Before and after are the real output of the app's lookup code, run before and after two files appear in a scratch folder Illustrative input. Button labels are the app's strings; the progress timing is drawn.

        5Download a print once, read it offline

        The mushaf tab shows the printed page as an image. The reader is adapted from the open-source Quran for Android project, and it follows the same shape as the listening side: before opening a print it checks that the pages are on the phone, and asks before downloading them.

        • Onboarding offers eight prints (OnboardingMushafFragment.java:50-67), and Madinah Colored is the default (OnboardingState.java:34).
        • Madinah Colored is this app's addition: 604 colour pages plus their ayah positions, served as one zip from the developer's own S3 bucket (MadinaColoredPageProvider.kt:29-32). The other seven come from android.quran.com.
        • The first time pages are missing, the reader asks “Download Required Files?” (QuranDataActivity.kt:430-496, from Quran for Android).
        • Read-along audio, also from Quran for Android, lists 48 recitations and plays from the phone whenever every file in the chosen range is already downloaded (AudioPresenter.kt:36-42).

        Eight prints, and the first open of the default

        1. Pages on the phone?Madinah Colored, 604 pagesNo
        2. Download Required Files?NoYesYes
        3. One zipzips/images_1000.zip from geohashim-quran, eu-central-1
        4. Reader opensAll 604 pages on the phone, no network needed after thisOpen
        Page counts, hosts and the per-page flag come from the published code. The prompt title is the app's string; its message and the download size are not shown. Saying No falls back to the Classic Madani print, which can fetch single pages (MadinaColoredPageProvider.kt:51-54).

        Keeping your place

        Bookmarks, tags and recently read pages live in a SQLite database on the phone; that module is adapted from Quran for Android, and it keeps the three most recent pages. Two pieces of memory are this app's own: the reader remembers the last repeat drill across restarts, and the listening player saves its queue and which surah it was on.

        Recent pages, three slots

        addLastPage {
          REPLACE INTO last_pages(page) VALUES(?);
          DELETE FROM last_pages WHERE _ID NOT IN(
            SELECT last_pages._ID FROM last_pages ORDER BY added_date DESC LIMIT ?
          );
        }

        Real output of the app's LastPage.sq run in SQLite, with the two edits plain SQLite needs: the SQLDelight type adapter removed, and the limit bound to 3 (Constants.MAX_RECENT_PAGES). The pages visited are illustrative.

        The last repeat drill

        Start and end ayah, verse repeats, range repeats and whether to stop at the end are saved the moment playback starts, so they survive the app being closed (QuranSettings.java:97-112). Reopening the panel inside that range restores them.

        The listening queue

        The surah list and the index of the current surah are kept in the app's preferences, and the playback service reads them from there each time it starts (StorageUtil.java:69, :107; MediaPlayerService.kt:157-159). The position inside a surah is held in memory only.

        Bookmarks and tags

        A bookmark is a page, or a surah and ayah; tags group them. Import, export and CSV export come with the Quran for Android module.

        Deliberately missing

        Single-page downloads for the default print
        The bucket publishes only the full zip. A missing page re-fetches the zip instead of asking for page files that don't exist.
        Live channels
        The Makkah and Madinah live streams are hidden in 13.0.0. Their only way in was a tile on the More tab; the code stays so they can come back.
        A Compose rewrite
        The app stays on XML views and Material 3; the onboarding redesign spec lists a Jetpack Compose migration as a non-goal.

        Try it

        Install it from Google Play, open the Quran tab and pick a reciter. Download a surah or two, then turn on airplane mode: the downloads still play, with the player showing “Offline”, and the rest stop with “Network Error”.

        Get it on Google Play