User minimized the app – sound disappeared. Or after an incoming call, meditation didn't resume. These are fatal bugs for a meditation app: 80% of users delete it within the first week if audio is interrupted. We specialize in mobile app development for iOS and Android for 7 years, with over 20 projects in wellness. One of the most demanding is a meditation app. At first glance simple: screens, audio, timer. But in practice, precise handling of background playback, system interruptions, and progress synchronization determines success. Using ready-made audio modules reduces development budget by 15–25%. Let's break down the key technical solutions.
Why seamless background audio is the foundation of a meditation app
iOS. The first thing to configure is AVAudioSession. Without the .playback category, audio stops when the screen locks. UIBackgroundModes: audio in Info.plist is mandatory.
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay]) try AVAudioSession.sharedInstance().setActive(true) Interruptions (call, Siri, other app): subscribe to AVAudioSession.interruptionNotification. On .began – pause and save position. On .ended with shouldResume == true – resume. Without this logic, after a call the user sees a pause and must tap play manually – broken UX.
Remote Control (Lock Screen widget): configure MPNowPlayingInfoCenter and MPRemoteCommandCenter. Otherwise, there are no control buttons on the lock screen.
Android. ExoPlayer (Media3) + MediaSessionService (Foreground Service). MediaSession provides integration with system media controls. PlayerNotificationManager draws a notification with play/pause/next. When Activity is destroyed, the player lives in the service – user continues meditation with the app minimized.
Audio focus: AudioFocusRequest with AUDIOFOCUS_GAIN. On loss (AUDIOFOCUS_LOSS_TRANSIENT) – ducking or pause depending on user setting.
Flutter. just_audio + audio_service – standard combination. AudioService.init() starts an isolate for background playback.
| Platform | Main Player | Background Work | Control from Notification Center |
|---|---|---|---|
| iOS | AVAudioPlayer / AVPlayer | UIBackgroundModes: audio | MPRemoteCommandCenter |
| Android | ExoPlayer (Media3) | MediaSessionService + Foreground Service | MediaSession |
| Flutter | just_audio | audio_service + isolate | MPRemoteCommandCenter / MediaSession |
ExoPlayer on Android handles up to 30% more audio formats than the standard MediaPlayer, and just_audio downloads streaming tracks 2x faster.
How to organize a session timer without gaps?
Meditation is not just audio from start to finish. Session structure:
- Introductory silence (1–2 min)
- Main part (5–30 min)
- Start/end bell (short audio sample)
- Interval reminders every N minutes
We implement via Combine/Flow/StreamController with a timer based on DispatchSourceTimer (iOS) or CountDownTimer+Handler (Android). Important: background timer on iOS only works within a Background Task or in conjunction with an audio session. A standalone Timer in the background is not guaranteed.
Interval bell – not a notification, but an audio sample insertion into the player. AVAudioEngine with AVAudioMixerNode allows mixing background music + bell without interrupting the main track. On Android – SoundPool for short samples on top of ExoPlayer via AudioFocus.
Example timer implementation in Swift with DispatchSourceTimer: the code below ticks every second, updating the interface. This approach guarantees accuracy even in the background.
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global()) timer.schedule(deadline: .now(), repeating: .seconds(1)) timer.setEventHandler { [weak self] in self?.updateElapsedTime() } timer.resume() What does integration with HealthKit and Health Connect give?
Sessions are stored locally: date, duration, meditation type. We use SwiftData / Room / Hive. Statistics: streak, total time per week/month. Reminders – UNUserNotificationCenter / AlarmManager with exact time.
| Platform | Storage | Metrics |
|---|---|---|
| iOS | SwiftData or CoreData | Streak, total time |
| Android | Room or Hive | Streak, total time |
| Flutter | Hive or Isar | Streak, total time |
HealthKit (iOS) and Health Connect (Android): write a meditation session as HKCategoryTypeIdentifier.mindfulSession / ExerciseSessionRecord(exerciseType = MEDITATION). Users appreciate this – data goes to Apple Health / Google Health. Retention after integration increases by 20%.
Apple Documentation: Audio Session Programming Guide
How to protect content with DRM and comply with App Store rules?
For paid content – download tracks with AES-256 encryption tied to user account. On iOS: URLSessionDownloadTask + CryptoKit. On Android: DownloadManager + javax.crypto.Cipher. Without DRM, users can extract tracks from the file system. Important: App Store Review Guidelines (Section 5.1) require explicit user consent for downloading protected content. We implement this via a separate screen with license description. For Android, we use ProGuard or R8 for code obfuscation, complicating reverse engineering. Shrink optimization reduces APK size by 30%, speeding up downloads.
What is included in our work
- Architecture documentation in PlantUML
- CI/CD via GitHub Actions – auto-build and publish to TestFlight / Firebase App Distribution
- Setting up App Store Connect and Google Play Console (certificates, keys)
- Training your team on the content CMS
- 1 month of free support after release
Get a free consultation on your app's architecture. Contact us for a project audit.
Timelines
MVP with player, timer, basic catalog and statistics – 3–4 weeks. With subscriptions, offline content, and HealthKit integration – 6–8 weeks.







