> ## Documentation Index
> Fetch the complete documentation index at: https://docs.run.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Simulator Audio

> Listen to simulator sound, control viewer mute, and capture speaker output with the SDK or CLI.

Listen to the sound emitted by an iOS simulator or Android emulator. Audio works
with any video codec. Each viewer starts muted; enabling sound affects that
viewer only. The simulator's volume controls still apply.

## Listen in the browser

Open the signed session viewer and press **Unmute simulator audio**. Press
**Mute simulator audio** to stop playback immediately. Reconnecting a viewer
starts muted again.

Browsers may require a click inside the simulator before allowing sound. If
playback is blocked, click inside the viewer and enable sound again. A capture
failure appears as an audio error while the simulator remains usable.

## Control an embedded viewer

The React ref exposes `setAudioMuted(muted)`. `onAudioStatus` reports `muted`,
`connecting`, `playing`, `blocked`, or `unavailable`.

```tsx theme={null}
"use client";

import { useRef, useState } from "react";
import { RemoteControl, type RemoteControlHandle } from "@runcloud/ui";

export function SimulatorWithSound({ url }: { url: string }) {
  const control = useRef<RemoteControlHandle>(null);
  const [muted, setMuted] = useState(true);
  const [error, setError] = useState<string | null>(null);

  return (
    <section>
      <div style={{ width: 390, height: 720 }}>
        <RemoteControl
          ref={control}
          url={url}
          onAudioStatus={(status) => {
            setMuted(status.muted);
            setError(status.error);
          }}
        />
      </div>
      <button onClick={() => control.current?.setAudioMuted(!muted)}>
        {muted ? "Enable sound" : "Mute sound"}
      </button>
      {error && <p role="alert">{error}</p>}
    </section>
  );
}
```

The return value indicates whether the message could be sent. Use
`onAudioStatus` to confirm playback. Preserve the default `allow` policy, which
includes `autoplay`; include `autoplay` if you override it.

For a raw iframe, send `{ type: "run-cloud:audio-control", muted: false }` to
the signed URL's exact origin. Receive `run-cloud:audio-status` with `state`,
`muted`, and an optional `error`. Verify both `event.source` and `event.origin`
against the current iframe. See [Embed a Simulator](/platform/embed-simulator).

## Stream speaker output with the SDK

`cloud.ios.streamAudio`, `cloud.android.streamAudio`, and
`cloud.simulators.streamAudio` return an async generator of
`SimulatorAudioFrame`. Each frame contains `pcm` bytes, `sampleRate: 44100`,
`channels: 2`, and `format: "s16le"`: interleaved signed 16-bit little-endian
stereo samples. Silence is valid audio; receiving a frame alone does not prove
that an app is emitting sound.

```ts theme={null}
import { Client } from "@run-cloud/sdk";

const cloud = new Client();
const session = await cloud.android.create({ hardTimeout: "2m" });
const stop = new AbortController();
const deadline = setTimeout(() => stop.abort(), 10_000);

try {
  for await (const frame of cloud.android.streamAudio(session.id, {
    signal: stop.signal,
  })) {
    // Send frame.pcm to your audio sink using frame.sampleRate and frame.channels.
  }
} catch (error) {
  if (!stop.signal.aborted) throw error;
} finally {
  clearTimeout(deadline);
  stop.abort();
  await cloud.android.delete(session.id);
}
```

Breaking out of the loop or aborting closes capture. The metered simulator
session remains active until you release it. The generic client accepts
`{ platform: "ios" | "android", signal }`, with iOS as its default platform.

## Record a WAV with the CLI

```bash theme={null}
runcloud ios audio "$IOS_SESSION_ID" --duration 10 --output ios.wav --json
runcloud android audio "$ANDROID_SESSION_ID" --duration 10 --output android.wav --json
```

The session must be active. `--duration` defaults to 10 seconds and accepts
0.1 to 60 seconds. Output is a stereo, 44.1 kHz, 16-bit PCM WAV. The CLI
preserves silence gaps and refuses to overwrite an existing output file.
Press Ctrl+C to cancel capture. Release the session separately when finished.

Viewer mute controls only browser playback. SDK and CLI capture can run while
the viewer is muted. This is speaker output; use microphone injection when you
need to send prerecorded sound into an app's microphone.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.