Choose the microphone

An embedded Eagle 3D Streaming page captures whichever microphone the operating system calls the default. Two commands let your own page offer the viewer a proper picker and switch device mid-session, without dropping the stream.

Without this, the only way a viewer can change input device is the browser's padlock menu or the operating system's sound settings. For anyone who is not comfortable in browser settings that may as well not exist — so the picker belongs on your page, where your users already are.

The order things have to happen in

The mistake everyone makes first, in one diagram

This is the part that catches people. You cannot get a useful device list before the browser has granted the microphone, and the browser only grants it when something asks — which the Eagle 3D page does when the session connects, not when your page loads.

 YOUR PAGE            THE PLAYER              THE BROWSER
     |                    |                        |
     | (1) page loads     |                        |
     |     iframe.src set |                        |
     |------------------->|                        |
     |                    |                        |
     |  X asking now is too early: no permission   |
     |    yet, and no audio track to swap          |
     |                    |                        |
     |                    | (2) session connects   |
     |                    |     getUserMedia(audio)|
     |                    |----------------------->|
     |                    |                        |
     |                    |          +-------------+------+
     |                    |          | "Allow microphone?" |
     |                    |          +-------------+------+
     |                    |                        |
     |                    |<-----------------------|
     |                    |   granted, default mic |
     |                    |                        |
     | (3) stage5_play... |                        |
     |<-------------------|                        |
     |                    |                        |
     | (4) getMicDevices  |                        |
     |------------------->| enumerateDevices()     |
     |                    |----------------------->|
     |                    |<-----------------------|
     |   micDevices       |    ids AND labels      |
     |<-------------------|                        |
     |                    |                        |
     | (5) viewer picks one                        |
     |     setMicDevice { deviceId }               |
     |------------------->| getUserMedia(deviceId) |
     |                    |----------------------->|
     |                    |<-----------------------|
     |                    | sender.replaceTrack()  |
     |                    | oldTrack.stop()        |
     |   micDeviceChanged |                        |
     |<-------------------|  no renegotiation:     |
     |                    |  the session stays up  |

What happens if you ask too early

What each command answers before permission is granted

Neither command throws. Both answer — and the answer tells you which wall you hit, which is why it is worth reading rather than ignoring.

getMicDevices before permission

                +------------------------------+
 getMicDevices  | Has the browser granted the  |
       |        | microphone yet?              |
       v        +--------------+---------------+
                   no          |          yes
           +-------------------+-------------------+
           v                                       v
  permissionGranted: false              permissionGranted: true
  deviceCount: 1                        devices: [
  devices: []                             { deviceId: "z9y8...",
                                            label: "Headset (Realtek)",
  the browser answered with ONE            isActive: true }
  placeholder - deviceId "",            ]
  label "" - which is not a
  device, so it is not listed           what you want

The two empty lists mean different things and need different words on your page. permissionGranted: false with a non-zero deviceCount means "allow the microphone, then list again" — there is something to do, and it is not on your page. No devices at all means there is no microphone, and no amount of allowing will produce one.

setMicDevice before there is a session

                +------------------------------+
 setMicDevice   | Is there an audio sender on  |
       |        | the live connection?         |
       v        +--------------+---------------+
                   no          |          yes
           +-------------------+-------------------+
           v                                       v
  ok: false                              getUserMedia({ deviceId })
  error: "no audio sender -                        |
    is the microphone enabled           +----------+----------+
    for this app?"                      v                     v
                                  ok: true              ok: false
  You asked before the             deviceId, label       error: "..."
  session was up, or
  "Enable Browser Microphone"     swapped live,         see the table
  is off in the dashboard         session intact        below

Reading the reply

Four error names, and a different sentence for each

error carries the browser's own error name, because the cases call for four different messages on your side.

errorWhat happenedWhat to tell the viewer
NotAllowedError The viewer has refused the microphone for this site "Allow the microphone in the padlock menu beside the address bar." Nothing on the page can override this.
OverconstrainedError The chosen device is no longer there "That microphone was disconnected." Re-list and let them pick again.
NotFoundError There is no matching input device Re-list.
NotReadableError The device exists but another application has it "Close whatever else is using the microphone and try again."
no audio sender… There is no audio track on the connection to swap You asked before the session was up, or the app is not configured with Enable Browser Microphone.

On success you get back the deviceId as the browser resolved it, plus the device's label — so your picker can confirm what it actually got rather than what it asked for.

Switching does not restart the stream

replaceTrack, and why the old track is stopped last
     before                setMicDevice              after
  +------------+                                 +------------+
  | RTCPeerConn|                                 | RTCPeerConn|
  |  +------+  |    sender.replaceTrack(new)     |  +------+  |
  |  |sender|--+--> default mic  ==========>     |  |sender|--+--> chosen mic
  |  +------+  |                                 |  +------+  |
  +------------+     no renegotiation            +------------+
                     no re-queue
   same session ------ no reconnect ------------> same session

replaceTrack() swaps the outgoing track on an established sender without renegotiating, so the call continues uninterrupted. The old track is stopped after the new one is acquired and swapped in — any other order means a device that has been unplugged since the list was built costs the viewer the microphone they still had.

How to check it actually worked

One row on the Eagle 3D page's own panel settles it

micDeviceChanged: ok says the command succeeded. It does not, by itself, prove the audio being sent to your app now comes from the device the viewer picked — a dropdown that changed its own label would look identical.

The Eagle 3D page has an information panel that settles it. Open it with the i button on the stream's control bar and find the Microphone block. Its last row is Device, and it is read live from the track the connection is actually sending:

track.getSettings().deviceId   // the sender's CURRENT track

Switch device and that row changes. That is the verification — it is the media layer reporting on itself, not the page repeating what it asked for.

RowAfter a successful switch
Devicechanges to a different id — this is the one that matters
Trackstays live — replaceTrack does not renegotiate
Packets sentkeeps climbing, no reset — the session never dropped
Permissionstays granted
Sample rate, Channels, Echo cancellationmay change, if the new device reports different settings

Devices appearing and disappearing

A headset plugged in mid-session refreshes your picker

The player watches the browser's devicechange event and re-sends micDevices on its own when the machine's inputs change. Plug in a headset mid-session and your picker refreshes without anyone pressing anything.

When the permission is the problem

Reset both sites, then hard reload — in that order

The fix that resolves most of it

Reset the permissions for both sites, then hard reload. Both steps, in that order — a reset does nothing to the page already open, because the permission was read when it loaded. Resetting without reloading looks exactly like the reset not working.

  1. Reset your own page

    Click the padlock beside the address bar → Site settings → Reset permissions.

  2. Reset the Eagle 3D website

    The one people miss, because it never appears in the address bar. In Chrome, go straight to it:

    chrome://settings/content/siteDetails?site=https://<your-connector>.eagle3dstreaming.com

    The connector hostname is the first part of your stream URL. Set Microphone to Allow, or press Reset permissions to start clean.

  3. Hard reload

    Ctrl+Shift+R, or Cmd+Shift+R on a Mac. Then allow the microphone when asked.

BrowserWhere the reset is
Chrome, Edge Padlock → Site settings → Reset permissions. For the Eagle 3D website, use the chrome://settings/content/siteDetails?site= address above.
Firefox Padlock → Clear cookies and site data, or Settings → Privacy & Security → Permissions → Microphone → Settings.
Safari Safari → Settings for This Website, or Settings → Websites → Microphone.

What each symptom means

What you seeWhat it means
No Microphone row in the padlock menu at all, and "No usage data" in site settings The browser never recorded a request. It refused before prompting — so there is nothing to reset on that website, and the cause is elsewhere.
Padlock says allowed, the stream says not granted Two different sites. Reset and allow the stream's origin as well.
NotAllowedError with no prompt ever shown Refused by policy or by a previous decision, not by the viewer just now.
Devices listed but all unnamed Permission has not been granted yet in that document. The devices are real; the names are withheld.
micDeviceChanged: no audio sender The session connected before the microphone was allowed, so it has no track to switch. Restart the stream.

A working page

Copy this
const frame = document.getElementById("streamFrame");

function send(message) {
  frame.contentWindow.postMessage(JSON.stringify(message), "*");
}

window.addEventListener("message", (event) => {
  const data = event.data;
  if (!data || !data.type) return;

  // Build the controls only once the video is playing - before that the
  // labels are empty and there is no track to swap.
  if (data.type === "stage5_playBtnPressed") {
    send({ cmd: "getMicDevices" });
  }

  // Can also arrive unprompted, when a device is plugged in or removed.
  if (data.type === "micDevices") {
    const select = document.getElementById("micPicker");
    select.innerHTML = "";
    data.devices.forEach((device, i) => {
      const option = document.createElement("option");
      option.value = device.deviceId;
      option.textContent = device.label || `Microphone ${i + 1}`;
      option.selected = device.isActive;
      select.appendChild(option);
    });
  }

  if (data.type === "micDeviceChanged") {
    if (data.ok) showStatus(`Now using ${data.label}`);
    else showStatus(describe(data.error));
  }
});

document.getElementById("micPicker").addEventListener("change", (e) => {
  send({ cmd: "setMicDevice", deviceId: e.target.value });
});

Before any of this works

Three things that must be true, and one that is not yours to set
RequirementWhere
The <iframe> carries allow="microphone" Your page's HTML. Read only when the frame's document loads — changing it later means reassigning src.
Enable Browser Microphone is on The app's configuration in the Control Panel. Without it there is no audio sender to swap a track on.
The viewer allows the microphone when asked The browser. If they refuse, only they can undo it.

Last updated