Audio Devices

Manage microphones, speakers, and audio settings in your PTT application.

Listing Available Devices

Get lists of available audio input (microphones) and output (speakers) devices:

TypeScript
// Get all microphones
const microphones = await talker.getAudioInputDevices();
console.log('Available microphones:', microphones);

// Get all speakers
const speakers = await talker.getAudioOutputDevices();
console.log('Available speakers:', speakers);

// Each device is a MediaDeviceInfo object:
// {
//   deviceId: string,
//   label: string,
//   kind: 'audioinput' | 'audiooutput',
//   groupId: string
// }
Permission Required

Device labels are only available after the user has granted microphone permission. Before permission is granted, label will be empty.

Selecting Devices

Select Microphone

TypeScript
const microphones = await talker.getAudioInputDevices();

// Select a specific microphone
await talker.setAudioInputDevice(microphones[1].deviceId);

// The change takes effect immediately for any active or future recordings

Select Speaker

TypeScript
const speakers = await talker.getAudioOutputDevices();

// Select a specific speaker
await talker.setAudioOutputDevice(speakers[0].deviceId);

// Audio playback will now use the selected speaker

Building a Device Picker UI

TypeScript
async function populateDeviceSelectors() {
  const micSelect = document.getElementById('mic-select') as HTMLSelectElement;
  const speakerSelect = document.getElementById('speaker-select') as HTMLSelectElement;

  // Get devices
  const microphones = await talker.getAudioInputDevices();
  const speakers = await talker.getAudioOutputDevices();

  // Populate microphone dropdown
  micSelect.innerHTML = '';
  microphones.forEach(device => {
    const option = document.createElement('option');
    option.value = device.deviceId;
    option.textContent = device.label || `Microphone ${micSelect.options.length + 1}`;
    micSelect.appendChild(option);
  });

  // Populate speaker dropdown
  speakerSelect.innerHTML = '';
  speakers.forEach(device => {
    const option = document.createElement('option');
    option.value = device.deviceId;
    option.textContent = device.label || `Speaker ${speakerSelect.options.length + 1}`;
    speakerSelect.appendChild(option);
  });

  // Handle selection changes
  micSelect.addEventListener('change', async () => {
    await talker.setAudioInputDevice(micSelect.value);
  });

  speakerSelect.addEventListener('change', async () => {
    await talker.setAudioOutputDevice(speakerSelect.value);
  });
}

Volume Control

TypeScript
// Set volume (0 to 1)
talker.setVolume(0.8);

// Volume slider example
const volumeSlider = document.getElementById('volume') as HTMLInputElement;
volumeSlider.addEventListener('input', () => {
  const volume = parseFloat(volumeSlider.value);
  talker.setVolume(volume);
});

Muting Audio

Mute Speaker

TypeScript
// Mute incoming audio
talker.setSpeakerEnabled(false);

// Unmute
talker.setSpeakerEnabled(true);

// Toggle mute button
let speakerMuted = false;
muteButton.addEventListener('click', () => {
  speakerMuted = !speakerMuted;
  talker.setSpeakerEnabled(!speakerMuted);
  muteButton.textContent = speakerMuted ? 'Unmute' : 'Mute';
});

Mute Microphone

TypeScript
// Disable microphone
talker.setMicrophoneEnabled(false);

// Re-enable microphone
talker.setMicrophoneEnabled(true);

Monitoring Audio Levels

Display real-time microphone input levels:

TypeScript
function startAudioMeter() {
  const meter = document.getElementById('audio-meter');

  function update() {
    // Returns 0-1
    const level = talker.getAudioLevel();

    // Update visual meter
    meter.style.width = `${level * 100}%`;

    // Color based on level
    if (level > 0.8) {
      meter.style.backgroundColor = '#ef4444'; // Red - too loud
    } else if (level > 0.1) {
      meter.style.backgroundColor = '#10b981'; // Green - good
    } else {
      meter.style.backgroundColor = '#64748b'; // Gray - quiet
    }

    requestAnimationFrame(update);
  }

  update();
}

Handling Device Changes

Detect when devices are connected or disconnected:

TypeScript
// Listen for device changes
navigator.mediaDevices.addEventListener('devicechange', async () => {
  console.log('Audio devices changed');

  // Refresh device lists
  await populateDeviceSelectors();

  // Optionally notify user
  showNotification('Audio devices updated');
});

Requesting Permissions

Request microphone permission before PTT:

TypeScript
async function requestMicrophonePermission(): Promise {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });

    // Stop the stream immediately - we just needed permission
    stream.getTracks().forEach(track => track.stop());

    return true;
  } catch (error) {
    if (error.name === 'NotAllowedError') {
      showError('Microphone permission denied. Please enable it in your browser settings.');
    } else if (error.name === 'NotFoundError') {
      showError('No microphone found. Please connect a microphone.');
    } else {
      showError('Failed to access microphone: ' + error.message);
    }

    return false;
  }
}

// Request permission on page load or button click
async function init() {
  const hasPermission = await requestMicrophonePermission();

  if (hasPermission) {
    // Now device labels will be available
    await populateDeviceSelectors();
  }
}

Complete Settings Panel

HTML
<div class="audio-settings">
  <h3>Audio Settings</h3>

  <div class="setting-group">
    <label for="mic-select">Microphone</label>
    <select id="mic-select"></select>
  </div>

  <div class="setting-group">
    <label for="speaker-select">Speaker</label>
    <select id="speaker-select"></select>
  </div>

  <div class="setting-group">
    <label for="volume">Volume</label>
    <input type="range" id="volume" min="0" max="1" step="0.1" value="1">
  </div>

  <div class="setting-group">
    <label>Input Level</label>
    <div class="meter-container">
      <div id="audio-meter"></div>
    </div>
  </div>

  <button id="test-mic">Test Microphone</button>
</div>
TypeScript
class AudioSettings {
  constructor(private talker: TalkerClient) {
    this.init();
  }

  private async init() {
    await this.populateDevices();
    this.setupEventListeners();
    this.startMeter();
  }

  private async populateDevices() {
    const micSelect = document.getElementById('mic-select') as HTMLSelectElement;
    const speakerSelect = document.getElementById('speaker-select') as HTMLSelectElement;

    const [microphones, speakers] = await Promise.all([
      this.talker.getAudioInputDevices(),
      this.talker.getAudioOutputDevices(),
    ]);

    micSelect.innerHTML = microphones.map(d =>
      `<option value="${d.deviceId}">${d.label || 'Microphone'}</option>`
    ).join('');

    speakerSelect.innerHTML = speakers.map(d =>
      `<option value="${d.deviceId}">${d.label || 'Speaker'}</option>`
    ).join('');
  }

  private setupEventListeners() {
    document.getElementById('mic-select')!.addEventListener('change', (e) => {
      this.talker.setAudioInputDevice((e.target as HTMLSelectElement).value);
    });

    document.getElementById('speaker-select')!.addEventListener('change', (e) => {
      this.talker.setAudioOutputDevice((e.target as HTMLSelectElement).value);
    });

    document.getElementById('volume')!.addEventListener('input', (e) => {
      this.talker.setVolume(parseFloat((e.target as HTMLInputElement).value));
    });

    navigator.mediaDevices.addEventListener('devicechange', () => {
      this.populateDevices();
    });
  }

  private startMeter() {
    const meter = document.getElementById('audio-meter')!;

    const update = () => {
      const level = this.talker.getAudioLevel();
      meter.style.width = `${level * 100}%`;
      requestAnimationFrame(update);
    };

    update();
  }
}