# `Mob.Audio`
[🔗](https://github.com/genericjam/mob/blob/master/lib/mob/audio.ex#L1)

Microphone recording and audio playback.

Recording requires `:microphone` permission (`Mob.Permissions.request/2`).
iOS additionally needs `NSMicrophoneUsageDescription` in
`Info.plist`; Android needs `RECORD_AUDIO` in
`AndroidManifest.xml`. The default `mix mob.new` templates ship
both. See the [permissions guide](permissions.html) for the
cross-platform table.

Playback requires no permission.

## Recording

    Mob.Audio.start_recording(socket, format: :aac, quality: :medium)
    Mob.Audio.stop_recording(socket)
    # → handle_info({:audio, :recorded, %{path: path, duration: seconds}}, socket)
    # → handle_info({:audio, :error,    reason},                            socket)

## Playback

    Mob.Audio.play(socket, "/path/to/file.m4a")
    Mob.Audio.play(socket, "/path/to/file.m4a", loop: true, volume: 0.8)
    Mob.Audio.stop_playback(socket)
    Mob.Audio.set_volume(socket, 0.5)
    # → handle_info({:audio, :playback_finished, %{path: path}}, socket)
    # → handle_info({:audio, :playback_error,    %{reason: reason}}, socket)

iOS: `AVAudioPlayer` / `AVPlayer`. Android: `MediaPlayer`.

# `format`

```elixir
@type format() :: :aac | :wav
```

# `quality`

```elixir
@type quality() :: :low | :medium | :high
```

# `play`

```elixir
@spec play(Mob.Socket.t(), String.t(), keyword()) :: Mob.Socket.t()
```

Play an audio file. Stops any currently playing audio first.

Options:
  - `loop: boolean` (default `false`)
  - `volume: float 0.0–1.0` (default `1.0`)

Result arrives as:
  - `{:audio, :playback_finished, %{path: path}}`
  - `{:audio, :playback_error, %{reason: reason}}`

# `play_at`

```elixir
@spec play_at(Mob.Socket.t(), String.t(), integer(), keyword()) :: Mob.Socket.t()
```

Schedule `path` to begin playing at absolute local wall-clock time
`at_wall_ms` (in `System.system_time(:millisecond)` terms — caller is
responsible for translating from a server-supplied target time via their
own clock-sync component).

The audio hardware clock — not BEAM's timer wheel — fires playback at
the requested instant. Multiple `play_at/3` calls accumulate on the
player's timeline (call `stop_playback/1` to flush). If `at_wall_ms` is
already in the past, the buffer plays as soon as the audio engine can.

Options:
  - `volume: float 0.0–1.0` (default `1.0`)

Result arrives as `{:audio, :playback_finished, %{path: path}}` when
the scheduled buffer drains, or `{:audio, :playback_error,
%{reason: reason}}` if the file fails to open.

iOS: `AVAudioEngine` + `AVAudioPlayerNode.scheduleBuffer(_:at:options:)`,
with the `at:` `AVAudioTime` constructed from `mach_absolute_time` so the
buffer starts at the requested host time.

Android: TODO — falls back to immediate playback on Android until the
AAudio port lands.

# `set_volume`

```elixir
@spec set_volume(Mob.Socket.t(), float()) :: Mob.Socket.t()
```

Adjust playback volume (0.0–1.0) without stopping playback.

# `start_recording`

```elixir
@spec start_recording(
  Mob.Socket.t(),
  keyword()
) :: Mob.Socket.t()
```

Start recording audio from the microphone.

Options:
  - `format: :aac | :wav` (default `:aac`)
  - `quality: :low | :medium | :high` (default `:medium`)

# `stop_playback`

```elixir
@spec stop_playback(Mob.Socket.t()) :: Mob.Socket.t()
```

Stop the currently playing audio.

# `stop_recording`

```elixir
@spec stop_recording(Mob.Socket.t()) :: Mob.Socket.t()
```

Stop the in-progress recording and save it to a temp file.
Result arrives as `{:audio, :recorded, %{path: ..., duration: ...}}`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
