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

# Media Management

> Managing microphones, cameras, speakers, and media tracks in Pipecat client applications.

<View title="React" icon="react">
  <Callout icon="react" color="#FFC107">
    You are currently viewing the React version of this page. Use the dropdown to the right to customize this page for your client framework.
  </Callout>
</View>

<View title="JavaScript" icon="js">
  <Callout icon="js" color="#FFC107">
    You are currently viewing the JavaScript version of this page. Use the dropdown to the right to customize this page for your client framework.
  </Callout>
</View>

<View title="React Native" icon="mobile">
  <Callout icon="mobile" color="#FFC107">
    You are currently viewing the React Native version of this page. Use the dropdown to the right to customize this page for your client framework.
  </Callout>
</View>

<View title="iOS" icon="apple">
  <Callout icon="apple" color="#FFC107">
    You are currently viewing the iOS version of this page. Use the dropdown to the right to customize this page for your client framework.
  </Callout>
</View>

The Pipecat client handles media at two levels: **local devices** (the user's mic, camera, and speakers) and **media tracks** (the live audio/video streams flowing between client and bot). This page covers how to work with both.

## Bot audio output

<View title="React" icon="react">
  Drop [`PipecatClientAudio`](/api-reference/client/react/components#pipecatclientaudio) inside your [`PipecatClientProvider`](/api-reference/client/react/components#pipecatclientprovider) and it handles everything — it mounts a hidden `<audio>` element and wires up the bot's audio track automatically:

  ```tsx theme={null}
  import { PipecatClientAudio, PipecatClientProvider } from "@pipecat-ai/client-react";

  ReactDOM.createRoot(document.getElementById("root")!).render(
    <PipecatClientProvider client={client}>
      <App />
      <PipecatClientAudio />  {/* bot voice plays through this */}
    </PipecatClientProvider>
  );
  ```

  No props required. Just make sure it lives inside the provider.
</View>

<View title="JavaScript" icon="js">
  You need to attach the bot's audio track to an `<audio>` element manually. Listen for `TrackStarted` and attach any incoming bot audio track:

  ```ts theme={null}
  import { RTVIEvent } from "@pipecat-ai/client-js";

  const audioEl = document.createElement("audio");
  audioEl.autoplay = true;
  document.body.appendChild(audioEl);

  client.on(RTVIEvent.TrackStarted, (track, participant) => {
    if (!participant?.local && track.kind === "audio") {
      audioEl.srcObject = new MediaStream([track]);
    }
  });
  ```
</View>

<View title="React Native" icon="mobile">
  Audio output is handled automatically by the platform via `DailyMediaManager` — no additional setup required. The bot's audio plays through the device speaker as soon as the session connects.

  ```tsx theme={null}
  import { PipecatClient } from "@pipecat-ai/client-js";
  import { RNSmallWebRTCTransport } from "@pipecat-ai/react-native-small-webrtc-transport";
  import { DailyMediaManager } from "@pipecat-ai/react-native-daily-media-manager";

  const client = new PipecatClient({
    transport: new RNSmallWebRTCTransport({ mediaManager: new DailyMediaManager() }),
    enableMic: true,
  });
  ```
</View>

<View title="iOS" icon="apple">
  Audio output is handled automatically by the SDK — no additional setup required. The bot's audio plays through the device speaker as soon as the session connects.
</View>

***

## Microphone

### Enabling and muting

<View title="React" icon="react">
  Set `enableMic: true` in the [constructor](/api-reference/client/js/client-constructor) to start the session with the mic active. This is the default:

  ```tsx theme={null}
  const client = new PipecatClient({
    transport: new DailyTransport(),
    enableMic: true,
  });
  ```
</View>

<View title="JavaScript" icon="js">
  Set `enableMic: true` in the [constructor](/api-reference/client/js/client-constructor) to start the session with the mic active. This is the default:

  ```tsx theme={null}
  const client = new PipecatClient({
    transport: new DailyTransport(),
    enableMic: true,
  });
  ```
</View>

<View title="React Native" icon="mobile">
  Set `enableMic: true` in the [constructor](/api-reference/client/js/client-constructor) to start the session with the mic active. This is the default:

  ```tsx theme={null}
  const client = new PipecatClient({
    transport: new DailyTransport(),
    enableMic: true,
  });
  ```
</View>

<View title="iOS" icon="apple">
  Pass `enableMic: true` to `PipecatClientOptions` to start the session with the mic active. This is the default:

  ```swift theme={null}
  // Inside a @MainActor function (e.g. your connect() method)
  let options = PipecatClientOptions(transport: SmallWebRTCTransport(), enableMic: true)
  let client = PipecatClient(options: options)
  ```
</View>

<View title="React" icon="react">
  Use [`usePipecatClientMicControl`](/api-reference/client/react/hooks#usepipecatclientmiccontrol) to mute and unmute within a component:

  ```tsx theme={null}
  import { usePipecatClientMicControl } from "@pipecat-ai/client-react";

  function MicButton() {
    const { enableMic, isMicEnabled } = usePipecatClientMicControl();

    return (
      <button onClick={() => enableMic(!isMicEnabled)}>
        {isMicEnabled ? "Mute" : "Unmute"}
      </button>
    );
  }
  ```

  Or use the headless [`PipecatClientMicToggle`](/api-reference/client/react/components#pipecatclientmictoggle) component, which provides the same state via a render prop.
</View>

<View title="JavaScript" icon="js">
  Call [`enableMic()`](/api-reference/client/js/client-methods#enablemic-enable-boolean) to mute and unmute. Check the current state with [`isMicEnabled`](/api-reference/client/js/client-methods#ismicenabled):

  ```ts theme={null}
  client.enableMic(false); // mute
  client.enableMic(true);  // unmute

  const muted = !client.isMicEnabled;
  ```
</View>

<View title="React Native" icon="mobile">
  Call [`enableMic()`](/api-reference/client/js/client-methods#enablemic-enable-boolean) to mute and unmute. Track state with `useState`:

  ```tsx theme={null}
  import { useState } from "react";
  import { Button } from "react-native";

  function MicButton() {
    const [isMicEnabled, setIsMicEnabled] = useState(true);

    const toggle = () => {
      const next = !isMicEnabled;
      client.enableMic(next);
      setIsMicEnabled(next);
    };

    return <Button title={isMicEnabled ? "Mute" : "Unmute"} onPress={toggle} />;
  }
  ```
</View>

<View title="iOS" icon="apple">
  Call `enableMic(enable:completion:)` to mute and unmute. Mark the enclosing function `@MainActor` so you can read and write published state directly inside the completion:

  ```swift theme={null}
  @MainActor
  func toggleMic() {
    client.enableMic(enable: !isMicEnabled) { [weak self] result in
      switch result {
      case .success():
        self.isMicEnabled = self.client.isMicEnabled ?? false
      case .failure(let error):
        print("Mic toggle error: \(error)")
      }
    }
  }
  ```

  Check current state with `client.isMicEnabled`.
</View>

### Switching microphones

<View title="React" icon="react">
  [`usePipecatClientMediaDevices`](/api-reference/client/react/hooks#usepipecatclientmediadevices) gives you a reactive device list and setters:

  ```tsx theme={null}
  import { usePipecatClientMediaDevices } from "@pipecat-ai/client-react";

  function MicSelector() {
    const { availableMics, selectedMic, updateMic } = usePipecatClientMediaDevices();

    return (
      <select value={selectedMic?.deviceId} onChange={(e) => updateMic(e.target.value)}>
        {availableMics.map((mic) => (
          <option key={mic.deviceId} value={mic.deviceId}>{mic.label}</option>
        ))}
      </select>
    );
  }
  ```
</View>

<View title="JavaScript" icon="js">
  Enumerate available devices with [`getAllMics()`](/api-reference/client/js/client-methods#getallmics), then switch by `deviceId` using [`updateMic()`](/api-reference/client/js/client-methods#updatemic):

  ```ts theme={null}
  const mics = await client.getAllMics();
  client.updateMic(mics[1].deviceId);
  ```

  Listen for `AvailableMicsUpdated` to react when devices are plugged in or removed:

  ```ts theme={null}
  client.on(RTVIEvent.AvailableMicsUpdated, (mics) => {
    renderMicList(mics);
  });
  ```
</View>

<View title="React Native" icon="mobile">
  Enumerate available devices with [`getAllMics()`](/api-reference/client/js/client-methods#getallmics), then switch by `deviceId` using [`updateMic()`](/api-reference/client/js/client-methods#updatemic):

  ```tsx theme={null}
  const mics = await client.getAllMics();
  client.updateMic(mics[1].deviceId);
  ```
</View>

<View title="iOS" icon="apple">
  Enumerate available microphones with `getAllMics()` and switch with `updateMic(micId:completion:)`:

  ```swift theme={null}
  let mics = client.getAllMics() // [MediaDeviceInfo]
  client.updateMic(micId: mics[1].id, completion: nil)
  ```

  Listen for device changes via the delegate:

  ```swift theme={null}
  func onAvailableMicsUpdated(mics: [MediaDeviceInfo]) {
    Task { @MainActor in
      self.availableMics = mics
    }
  }
  ```
</View>

***

## Camera

<View title="React" icon="react">
  Camera is disabled by default. Enable it in the [constructor](/api-reference/client/js/client-constructor):

  ```tsx theme={null}
  const client = new PipecatClient({
    transport: new DailyTransport(),
    enableCam: true,
  });
  ```
</View>

<View title="JavaScript" icon="js">
  Camera is disabled by default. Enable it in the [constructor](/api-reference/client/js/client-constructor):

  ```tsx theme={null}
  const client = new PipecatClient({
    transport: new DailyTransport(),
    enableCam: true,
  });
  ```
</View>

<View title="React Native" icon="mobile">
  Camera is disabled by default. Enable it in the [constructor](/api-reference/client/js/client-constructor):

  ```tsx theme={null}
  const client = new PipecatClient({
    transport: new DailyTransport(),
    enableCam: true,
  });
  ```
</View>

<View title="iOS" icon="apple">
  Camera is disabled by default. Pass `enableCam: true` to `PipecatClientOptions` to enable it at session start:

  ```swift theme={null}
  // Inside a @MainActor function (e.g. your connect() method)
  let options = PipecatClientOptions(
    transport: SmallWebRTCTransport(),
    enableMic: true,
    enableCam: true
  )
  let client = PipecatClient(options: options)
  ```
</View>

<Note>
  Not all transports support video. `enableCam` has no effect on transports that
  don't support it (e.g. WebSocket).
</Note>

<View title="React" icon="react">
  Use [`usePipecatClientCamControl`](/api-reference/client/react/hooks#usepipecatclientcamcontrol) to toggle the camera, and [`PipecatClientVideo`](/api-reference/client/react/components#pipecatclientvideo) to render the feed:

  ```tsx theme={null}
  import { usePipecatClientCamControl } from "@pipecat-ai/client-react";

  const { enableCam, isCamEnabled } = usePipecatClientCamControl();
  ```

  ```tsx theme={null}
  import { PipecatClientVideo } from "@pipecat-ai/client-react";

  <PipecatClientVideo participant="local" fit="cover" mirror />
  ```

  Use `participant="bot"` to render the bot's video feed instead. Switch cameras via `availableCams` / `updateCam` from [`usePipecatClientMediaDevices`](/api-reference/client/react/hooks#usepipecatclientmediadevices).
</View>

<View title="JavaScript" icon="js">
  Toggle the camera with [`enableCam()`](/api-reference/client/js/client-methods#enablecam-enable-boolean) and check state with [`isCamEnabled`](/api-reference/client/js/client-methods#iscamenabled):

  ```ts theme={null}
  client.enableCam(true);
  const isOn = client.isCamEnabled;
  ```

  To render the local video feed, attach the track to a `<video>` element after the session starts:

  ```ts theme={null}
  client.on(RTVIEvent.TrackStarted, (track, participant) => {
    if (participant?.local && track.kind === "video") {
      const videoEl = document.getElementById("local-video") as HTMLVideoElement;
      videoEl.srcObject = new MediaStream([track]);
    }
  });
  ```

  Switch cameras with [`getAllCams()`](/api-reference/client/js/client-methods#getallcams) / [`updateCam()`](/api-reference/client/js/client-methods#updatecam).
</View>

<View title="React Native" icon="mobile">
  Toggle the camera with [`enableCam()`](/api-reference/client/js/client-methods#enablecam-enable-boolean):

  ```tsx theme={null}
  client.enableCam(true);
  const isOn = client.isCamEnabled;
  ```

  Switch cameras with [`getAllCams()`](/api-reference/client/js/client-methods#getallcams) / [`updateCam()`](/api-reference/client/js/client-methods#updatecam). To render video, use the `DailyMediaManager`'s video rendering capabilities per the React Native Daily SDK docs.
</View>

<View title="iOS" icon="apple">
  Toggle the camera during a session with `enableCam(enable:completion:)`:

  ```swift theme={null}
  @MainActor
  func toggleCam() {
    client.enableCam(enable: !isCamEnabled) { [weak self] result in
      switch result {
      case .success():
        self.isCamEnabled = self.client.isCamEnabled ?? false
      case .failure(let error):
        print("Cam toggle error: \(error)")
      }
    }
  }
  ```

  Switch cameras with `getAllCams()` and `updateCam(camId:completion:)`:

  ```swift theme={null}
  let cams = client.getAllCams() // [MediaDeviceInfo]
  client.updateCam(camId: cams[1].id, completion: nil)
  ```

  Receive track events to store the video track for rendering:

  ```swift theme={null}
  func onTrackStarted(track: MediaStreamTrack, participant: Participant?) {
    Task { @MainActor in
      if track.kind == .video {
        if participant?.local ?? true {
          self.localCamTrackId = track.id
        } else {
          self.botCamTrackId = track.id
        }
      }
    }
  }
  ```
</View>

***

## Speakers

<View title="React" icon="react">
  Enumerate and switch output devices via [`usePipecatClientMediaDevices`](/api-reference/client/react/hooks#usepipecatclientmediadevices):

  ```tsx theme={null}
  const { availableSpeakers, selectedSpeaker, updateSpeaker } = usePipecatClientMediaDevices();
  ```
</View>

<View title="JavaScript" icon="js">
  Enumerate and switch output devices with [`getAllSpeakers()`](/api-reference/client/js/client-methods#getallspeakers) / [`updateSpeaker()`](/api-reference/client/js/client-methods#updatespeaker):

  ```ts theme={null}
  const speakers = await client.getAllSpeakers();
  client.updateSpeaker(speakers[0].deviceId);
  ```
</View>

<View title="React Native" icon="mobile">
  Audio routing (speaker vs. earpiece) is managed by the platform and `DailyMediaManager`. To switch between speaker and earpiece, use `updateSpeaker()` or `updateMic()` — on mobile, changing the audio input and output are linked, so either method has the same effect:

  ```tsx theme={null}
  const speakers = await client.getAllSpeakers();
  client.updateSpeaker(speakers[0].deviceId);
  ```
</View>

<View title="iOS" icon="apple">
  To switch between speaker and earpiece, use `updateSpeaker()` or `updateMic()` — on mobile, changing the audio input and output are linked, so either method has the same effect:

  ```swift theme={null}
  const speakers = await self.client.getAllSpeakers();
  self.client.updateSpeaker(speakers[0].deviceId);
  ```
</View>

***

## Device initialization before connecting

By default, device access is requested when `connect()` is called. If you want to enumerate or test devices before the session starts — for example, to show a device picker pre-call — call [`initDevices()`](/api-reference/client/js/client-methods#initdevices) first:

```tsx theme={null}
await client.initDevices();
// devices are now available; user hasn't connected yet
const mics = await client.getAllMics();
```

***

## Media tracks

For advanced use cases — custom rendering, audio processing, recording — you can access the raw `MediaStreamTrack` objects directly.

<View title="React" icon="react">
  Use [`usePipecatClientMediaTrack`](/api-reference/client/react/hooks#usepipecatclientmediatrack):

  ```tsx theme={null}
  import { usePipecatClientMediaTrack } from "@pipecat-ai/client-react";

  function CustomAudioViz() {
    const botAudio = usePipecatClientMediaTrack("audio", "bot");

    useEffect(() => {
      if (!botAudio) return;
      // attach to Web Audio API, recorder, etc.
    }, [botAudio]);
  }
  ```
</View>

<View title="JavaScript" icon="js">
  Use [`tracks()`](/api-reference/client/js/client-methods#tracks) to access tracks synchronously once the session is ready:

  ```ts theme={null}
  const { local, bot } = client.tracks();
  // local.audio, local.video, bot.audio, bot.video
  ```

  Or listen for [`TrackStarted`](/api-reference/client/js/callbacks#media-events) to be notified as tracks become available:

  ```ts theme={null}
  client.on(RTVIEvent.TrackStarted, (track, participant) => {
    // attach to Web Audio API, recorder, etc.
  });
  ```
</View>

<View title="React Native" icon="mobile">
  Use [`tracks()`](/api-reference/client/js/client-methods#tracks) to access tracks synchronously once the session is ready:

  ```tsx theme={null}
  const { local, bot } = client.tracks();
  // local.audio, local.video, bot.audio, bot.video
  ```

  Or listen for `TrackStarted` to be notified as tracks become available:

  ```tsx theme={null}
  useEffect(() => {
    const handler = (track, participant) => {
      // process track as needed
    };
    client.on(RTVIEvent.TrackStarted, handler);
    return () => client.off(RTVIEvent.TrackStarted, handler);
  }, []);
  ```
</View>

<View title="iOS" icon="apple">
  Use `client.tracks()` to get the current tracks synchronously once the session is ready, or implement `onTrackStarted` in your delegate to be notified as they arrive:

  ```swift theme={null}
  // Synchronous access once connected
  let tracks = client.tracks
  // tracks?.local.audio, tracks?.local.video, tracks?.bot?.audio, tracks?.bot?.video

  // Or via delegate
  func onTrackStarted(track: MediaStreamTrack, participant: Participant?) {
    Task { @MainActor in
      switch (track.kind, participant?.local) {
      case (.audio, false):
        // bot audio track — handled automatically
        break
      case (.video, true):
        self.localCamTrackId = track.id
      case (.video, _):
        self.botCamTrackId = track.id
      default:
        break
      }
    }
  }
  ```
</View>

***

## Audio visualization

<View title="React" icon="react">
  The React SDK includes [`VoiceVisualizer`](/api-reference/client/react/components#voicevisualizer), a canvas-based component that renders audio level bars for any participant:

  ```tsx theme={null}
  import { VoiceVisualizer } from "@pipecat-ai/client-react";

  <VoiceVisualizer
    participantType="local"
    barColor="#3b82f6"
    barCount={5}
    barWidth={4}
    barMaxHeight={24}
  />
  ```

  Set `participantType="bot"` to visualize the bot's audio instead.
</View>

<View title="JavaScript" icon="js">
  Listen for `LocalAudioLevel` and `RemoteAudioLevel` events, which fire continuously with gain values between 0 and 1, and drive your own visualization:

  ```ts theme={null}
  const canvas = document.getElementById("viz") as HTMLCanvasElement;
  const ctx = canvas.getContext("2d")!;

  client.on(RTVIEvent.LocalAudioLevel, (level) => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "#3b82f6";
    ctx.fillRect(0, 0, canvas.width * level, canvas.height);
  });
  ```
</View>

<View title="React Native" icon="mobile">
  Listen for `LocalAudioLevel` and `RemoteAudioLevel` events and drive your own visualization with `Animated` or any drawing library:

  ```tsx theme={null}
  import { RTVIEvent } from "@pipecat-ai/client-js";
  import { useState, useEffect } from "react";
  import { View } from "react-native";

  function AudioViz() {
    const [level, setLevel] = useState(0);

    useEffect(() => {
      const handler = (l) => setLevel(l);
      client.on(RTVIEvent.LocalAudioLevel, handler);
      return () => client.off(RTVIEvent.LocalAudioLevel, handler);
    }, []);

    return (
      <View style={{ width: 200, height: 8, backgroundColor: "#eee" }}>
        <View style={{ width: 200 * level, height: 8, backgroundColor: "#3b82f6" }} />
      </View>
    );
  }
  ```
</View>

<View title="iOS" icon="apple">
  Receive audio level callbacks via the delegate and drive your own SwiftUI visualization:

  ```swift theme={null}
  @Published var localAudioLevel: Float = 0
  @Published var remoteAudioLevel: Float = 0

  func onLocalAudioLevel(level: Float) {
    Task { @MainActor in
      self.localAudioLevel = level
    }
  }

  func onRemoteAudioLevel(level: Float, participant: Participant) {
    Task { @MainActor in
      self.remoteAudioLevel = level
    }
  }
  ```

  ```swift theme={null}
  // In your SwiftUI view
  GeometryReader { geo in
    Rectangle()
      .fill(Color.blue)
      .frame(width: geo.size.width * CGFloat(model.localAudioLevel))
  }
  .frame(height: 8)
  ```
</View>

***

## API reference

<View title="React" icon="react">
  <CardGroup cols={2}>
    <Card title="Client Methods — Devices" icon="js" href="/api-reference/client/js/client-methods#devices">
      `initDevices`, `getAllMics`, `enableMic`, and more
    </Card>

    <Card title="React Hooks" icon="react" href="/api-reference/client/react/hooks">
      `usePipecatClientMediaDevices`, `usePipecatClientMicControl`, `usePipecatClientCamControl`, `usePipecatClientMediaTrack`
    </Card>

    <Card title="React Components" icon="puzzle-piece" href="/api-reference/client/react/components">
      `PipecatClientAudio`, `PipecatClientVideo`, `PipecatClientMicToggle`, `PipecatClientCamToggle`, `VoiceVisualizer`
    </Card>
  </CardGroup>
</View>

<View title="JavaScript" icon="js">
  <CardGroup cols={1}>
    <Card title="Client Methods — Devices" icon="js" href="/api-reference/client/js/client-methods#devices">
      `initDevices`, `getAllMics`, `updateMic`, `enableMic`, `tracks`, and more
    </Card>
  </CardGroup>
</View>

<View title="React Native" icon="mobile">
  <CardGroup cols={1}>
    <Card title="Client Methods — Devices" icon="js" href="/api-reference/client/js/client-methods#devices">
      `initDevices`, `getAllMics`, `updateMic`, `enableMic`, `tracks`, and more
    </Card>
  </CardGroup>
</View>

<View title="iOS" icon="apple">
  <CardGroup cols={1}>
    <Card title="iOS SDK Reference" icon="apple" href="/api-reference/client/ios/overview">
      Full API reference including `PipecatClientDelegate` and device management
    </Card>
  </CardGroup>
</View>
