Dala.Background (dala v0.0.1)

Copy Markdown View Source

Background execution keep-alive via a silent audio session.

iOS suspends apps when the screen locks unless they hold an active background execution mode. keep_alive/0 starts a silent AVAudioEngine looping a zero-filled buffer with AVAudioSessionCategoryOptionMixWithOthers — the OS sees an active audio session and keeps the process running, the user hears nothing, and any music already playing is undisturbed.

Requirements

The app's Info.plist must declare the audio background mode:

<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>

This is included in all projects generated by mix dala.new. For Xcode projects, add it under Signing & Capabilities → Background Modes → Audio, AirPlay, and Picture in Picture.

Usage

# Keep the app alive when the screen locks (e.g. in mount/2):
Dala.Background.keep_alive()

# Allow suspension again when background execution is no longer needed:
Dala.Background.stop()

keep_alive/0 is idempotent — safe to call multiple times.

Coexistence with Dala.Audio

Playback (Dala.Audio.play/3): both sides use MixWithOthers, so they mix transparently. The silent buffer is inaudible alongside real audio.

Recording (Dala.Audio.start_recording/2): recording switches the global AVAudioSession category to PlayAndRecord, which sends an interruption to the keep-alive engine. The engine stops — but the recording itself holds an active audio session, so the app stays alive for the duration of the recording. When stop_recording/1 is called and the session is released, iOS fires AVAudioSessionInterruptionTypeEnded and the keep-alive engine restarts automatically. No Elixir code is needed to handle this transition.

The same automatic restart applies to phone calls and any other event that temporarily takes the audio session away from the app.

Known limitation — observer cleanup

Internally, the NIF registers an NSNotificationCenter observer for AVAudioSessionInterruptionNotification to handle the recording restart described above. When stop/0 is called, it removes observers using removeObserver:nil scoped to that notification name, which removes all observers for that notification in the process — not just the one registered by this module.

In practice this is harmless because dala owns the audio session for all Dala.Audio functions, and none of them rely on surviving this cleanup. However, if you integrate a third-party audio library that registers its own AVAudioSessionInterruptionNotification observer, calling stop/0 will silently remove that observer too. In that case, avoid calling stop/0 while the third-party library is active, or file an issue so the NIF can be updated to store and remove only its own observer token.

Apple's stance

Apple permits the audio background mode for apps that legitimately use audio. Dala apps that use Dala.Audio recording or playback qualify. Apple will reject apps that declare this mode without any audio feature — do not add UIBackgroundModes: [audio] to an app that has no audio functionality.

Android — Foreground Service

On Android the OS equivalent of iOS background execution is a foreground service. keep_alive/0 starts BeamForegroundService, which calls startForeground/2 with a low-priority persistent notification. The OS will not kill a foreground service under memory pressure and will not pause the process when the screen locks.

Visible notification (required by Android)

Android requires every foreground service to post a visible notification. The notification appears in the status bar and notification tray with the app name and the text "Running in background". It has IMPORTANCE_LOW so it produces no sound or vibration. There is no API to hide it — this is an OS-level constraint designed to inform users when apps are running in the background.

Manifest requirements

mix dala.new adds the necessary declarations automatically:

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"
    android:minSdkVersion="34" />

<service android:name=".BeamForegroundService"
    android:exported="false"
    android:foregroundServiceType="dataSync" />

For apps created before this feature was added, copy the above snippet manually into your AndroidManifest.xml.

Stop behaviour

stop/0 sends ACTION_STOP to the service, which calls stopForeground and stopSelf. The OS removes the notification immediately. If the BEAM node goes silent (no incoming distribution traffic) the OS may still eventually kill the process — keep_alive/0 prevents aggressive background process killing but not an eventual idle OOM kill after many hours of complete inactivity.

Summary

Functions

Starts a silent audio session to prevent iOS from suspending the app when the screen locks. Idempotent — safe to call more than once.

Stops the keep-alive audio session and allows iOS to suspend the app normally when it goes to background.

Functions

keep_alive()

@spec keep_alive() :: :ok

Starts a silent audio session to prevent iOS from suspending the app when the screen locks. Idempotent — safe to call more than once.

stop()

@spec stop() :: :ok

Stops the keep-alive audio session and allows iOS to suspend the app normally when it goes to background.