Skip to main content
Camera glasses have cameras for photo/video but no displays. Example: Mentra Live. These devices are ideal for visual capture, streaming, and audio-based interactions.

Mentra Live

  • Camera: 1080p with streaming
  • Display: No
  • Microphone: Yes (with VAD)
  • Speaker: Yes
  • Buttons: Yes (press, double press, long press)
  • LEDs: RGB + white privacy light
  • WiFi: Yes
// Mentra Live capabilities
{
  modelName: "Mentra Live",
  hasCamera: true,
  hasDisplay: false,
  hasMicrophone: true,
  hasSpeaker: true,
  hasButton: true,
  hasLight: true,
  hasWifi: true
}

Building for Camera Glasses

Check Camera Capabilities

protected async onSession(session: AppSession, sessionId: string, userId: string) {
  const caps = session.capabilities;

  if (!caps?.hasCamera) {
    session.logger.warn('No camera available');
    return;
  }

  const camera = caps.camera;
  if (camera) {
    session.logger.info('Resolution:', camera.resolution);
    session.logger.info('Can stream:', camera.video.canStream);
    session.logger.info('Can record:', camera.video.canRecord);
  }
}

Photo Capture

if (caps.hasCamera) {
  const photo = await session.camera.requestPhoto({
    saveToGallery: true,
    size: 'large'
  });

  session.logger.info('Photo captured:', photo.filename);
  await session.audio.speak('Photo captured');
}

Video Streaming

if (caps.camera?.video.canStream) {
  await session.camera.startStream({
    rtmpUrl: 'rtmp://live.example.com/stream/key',
    video: {
      resolution: '1280x720',
      framerate: 30
    }
  });

  await session.audio.speak('Streaming started');
}

Audio-First Interactions

Without displays, use voice for feedback:
protected async onSession(session: AppSession, sessionId: string, userId: userId: string) {
  // Speak instead of showing text
  await session.audio.speak('Camera ready. Say "photo" to capture.');

  session.events.onTranscription(async (data) => {
    if (!data.isFinal) return;

    if (data.text.toLowerCase().includes('photo')) {
      await this.takePhoto(session);
    }
  });
}

private async takePhoto(session: AppSession) {
  await session.audio.speak('Taking photo');

  const photo = await session.camera.requestPhoto();

  await session.audio.speak('Photo saved');
}

Using Buttons

if (caps.hasButton) {
  session.events.onButtonPress(async (data) => {
    if (data.button === 'select') {
      await session.camera.requestPhoto();
      await session.audio.speak('Photo captured');
    }
  });
}

Using LEDs

if (caps.hasLight && caps.light) {
  // Check for RGB LED
  const hasRGB = caps.light.lights?.some(led => led.isFullColor);

  if (hasRGB) {
    // Show colored feedback
    await session.led.turnOn({
      color: 'green',
      ontime: 1000
    });
  }
}

WiFi Connectivity

if (caps.hasWifi) {
  session.logger.info('Device has WiFi');
  // Can stream without phone tethering
}

Camera Glass Best Practices

Provide audio feedback:
// ✅ Good - user knows what's happening
await session.audio.speak('Processing');
await processData();
await session.audio.speak('Complete');

// ❌ Avoid - silent operation confuses users
await processData();
Use speaker for output:
if (caps.hasSpeaker) {
  // Audio plays directly on glasses
  await session.audio.speak('Hello!');
} else {
  // Fallback to phone
  await session.audio.speak('Hello!');
}
Combine buttons and voice:
// Button for quick actions
session.events.onButtonPress(async (data) => {
  await session.camera.requestPhoto();
});

// Voice for complex commands
session.events.onTranscription(async (data) => {
  if (data.isFinal) {
    await this.handleCommand(data.text);
  }
});

Example App

class CameraGlassesApp extends AppServer {
  protected async onSession(session: AppSession, sessionId: string, userId: string) {
    const caps = session.capabilities;

    if (!caps?.hasCamera) {
      await session.audio.speak('Camera not available');
      return;
    }

    // Audio welcome
    await session.audio.speak('Camera app ready');

    // Button to capture
    session.events.onButtonPress(async (data) => {
      if (data.button === 'select') {
        await this.capturePhoto(session);
      }
    });

    // Voice commands
    session.events.onTranscription(async (data) => {
      if (!data.isFinal) return;

      const text = data.text.toLowerCase();

      if (text.includes('photo')) {
        await this.capturePhoto(session);
      } else if (text.includes('stream')) {
        await this.startStreaming(session);
      }
    });
  }

  private async capturePhoto(session: AppSession) {
    await session.audio.speak('Capturing');

    // LED feedback
    if (session.capabilities?.hasLight) {
      await session.led.blink({
        color: 'white',
        ontime: 100,
        offtime: 100,
        count: 2
      });
    }

    const photo = await session.camera.requestPhoto({
      saveToGallery: true
    });

    await session.audio.speak('Photo saved');
  }

  private async startStreaming(session: AppSession) {
    await session.audio.speak('Starting stream');

    await session.camera.startManagedStream({
      video: {
        resolution: '1280x720',
        framerate: 30
      }
    });

    await session.audio.speak('Streaming');
  }
}

Next Steps