meta_wearables_flutter 0.1.0 copy "meta_wearables_flutter: ^0.1.0" to clipboard
meta_wearables_flutter: ^0.1.0 copied to clipboard

A Flutter plugin for Meta Wearables Device Access Toolkit (MWDAT).

example/lib/main.dart

// example/lib/main.dart
//
// Example app demonstrating the meta_wearables_flutter plugin.
// Shows registration flow, device listing, camera streaming, and photo capture.

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:meta_wearables_flutter/meta_wearables_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // ── Step 1: Initialize the SDK ────────────────────────────────────────────
  await MetaWearables.initialize();

  runApp(const MetaGlassesExampleApp());
}

class MetaGlassesExampleApp extends StatelessWidget {
  const MetaGlassesExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Meta Wearables Example',
      theme: ThemeData.dark(useMaterial3: true),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  RegistrationState _registrationState = RegistrationState.unregistered;
  List<WearableDevice> _devices = [];
  CameraStream? _cameraStream;
  StreamState _streamState = StreamState.stopped;
  Uint8List? _currentFrame;
  PhotoData? _lastPhoto;
  String? _error;
  
  String? _displaySessionId;
  String? _displayId;

  @override
  void initState() {
    super.initState();
    _listenToRegistration();
    _listenToDevices();
  }

  void _listenToRegistration() {
    // ── Step 2: Observe registration state ──────────────────────────────────
    MetaWearables.registrationState.listen((state) {
      setState(() => _registrationState = state);
    });
  }

  void _listenToDevices() {
    MetaWearables.devices.listen((devices) {
      setState(() => _devices = devices);
    });
  }

  Future<void> _register() async {
    try {
      // ── Step 3: Launch Meta AI registration ─────────────────────────────
      await MetaWearables.startRegistration();
    } on WearablesError catch (e) {
      setState(() => _error = e.description);
    }
  }

  Future<void> _startStreaming() async {
    try {
      // ── Step 4: Check + request camera permission ────────────────────────
      var permission = await MetaWearables.checkCameraPermission();
      if (permission != PermissionStatus.granted) {
        permission = await MetaWearables.requestCameraPermission();
      }
      if (permission != PermissionStatus.granted) {
        setState(() => _error = 'Camera permission denied');
        return;
      }

      // ── Step 5: Start the camera stream ─────────────────────────────────
      final stream = await MetaWearables.startCameraStream(
        config: const StreamConfiguration(
          videoQuality: VideoQuality.medium,
          frameRate: FrameRate.fps24,
        ),
      );
      _cameraStream = stream;

      // Listen to state changes
      MetaWearables.streamStateChanges.listen((state) {
        setState(() => _streamState = state);
      });

      // Listen to video frames — update UI with each frame
      stream.frames.listen((frameBytes) {
        setState(() => _currentFrame = frameBytes);
      });
    } on WearablesError catch (e) {
      setState(() => _error = e.description);
    }
  }

  Future<void> _stopStreaming() async {
    await _cameraStream?.dispose();
    setState(() {
      _cameraStream = null;
      _currentFrame = null;
      _streamState = StreamState.stopped;
    });
  }

  Future<void> _capturePhoto() async {
    try {
      await _cameraStream?.capturePhoto();
    } on WearablesError catch (e) {
      setState(() => _error = e.description);
    }
  }

  Future<void> _showDisplayScreen() async {
    try {
      final sessionId = await MetaWearables.createSession();
      setState(() => _displaySessionId = sessionId);
      await MetaWearables.startSession(sessionId);

      final displayId = await MetaWearables.addDisplay(sessionId);
      setState(() => _displayId = displayId);

      await MetaWearables.sendDisplayContent(
        displayId, 
        "Hello from Flutter", 
        "This is a test of the Display capability.",
      );
    } on WearablesError catch (e) {
      setState(() => _error = e.description);
    }
  }

  Future<void> _stopDisplay() async {
    try {
      if (_displaySessionId != null && _displayId != null) {
        await MetaWearables.removeDisplay(_displaySessionId!, _displayId!);
      }
      if (_displaySessionId != null) {
        await MetaWearables.stopSession(_displaySessionId!);
      }
    } finally {
      setState(() {
        _displaySessionId = null;
        _displayId = null;
      });
    }
  }

  Future<void> _routeMicHFP() async {
    try {
      await MetaWearables.routeAudioToBluetooth(true);
      if (mounted) setState(() => _error = 'Routed Audio to HFP');
    } catch (e) {
      if (mounted) setState(() => _error = e.toString());
    }
  }
  
  Future<void> _routeA2DP() async {
    try {
      await MetaWearables.routeAudioToBluetooth(false);
      if (mounted) setState(() => _error = 'Routed Audio to A2DP');
    } catch (e) {
      if (mounted) setState(() => _error = e.toString());
    }
  }

  @override
  void dispose() {
    _cameraStream?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Meta Glasses')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Error banner
          if (_error != null)
            Card(
              color: Colors.red.shade900,
              child: Padding(
                padding: const EdgeInsets.all(12),
                child: Row(children: [
                  const Icon(Icons.error_outline, color: Colors.white),
                  const SizedBox(width: 8),
                  Expanded(
                      child: Text(_error!,
                          style: const TextStyle(color: Colors.white))),
                  IconButton(
                    icon: const Icon(Icons.close, color: Colors.white),
                    onPressed: () => setState(() => _error = null),
                  )
                ]),
              ),
            ),

          // ── Registration ──────────────────────────────────────────────────
          _SectionCard(
            title: 'Registration',
            child: Column(children: [
              _StatusRow('State', _registrationState.name),
              const SizedBox(height: 8),
              Row(children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed:
                        _registrationState != RegistrationState.registered
                            ? _register
                            : null,
                    icon: const Icon(Icons.link),
                    label: const Text('Register with Meta AI'),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton.icon(
                  onPressed: _registrationState == RegistrationState.registered
                      ? () => MetaWearables.startUnregistration()
                      : null,
                  icon: const Icon(Icons.link_off),
                  label: const Text('Unregister'),
                ),
              ]),
            ]),
          ),

          // ── Devices ───────────────────────────────────────────────────────
          _SectionCard(
            title: 'Connected Devices (${_devices.length})',
            child: _devices.isEmpty
                ? const Text(
                    'No devices found. Make sure glasses are paired and Meta AI app is open.')
                : Column(
                    children: _devices
                        .map((d) => ListTile(
                              leading: Icon(
                                Icons.remove_red_eye,
                                color:
                                    d.isConnected ? Colors.green : Colors.grey,
                              ),
                              title: Text(d.name),
                              subtitle: Text('ID: ${d.id}'),
                              trailing: Text(
                                  d.isConnected ? 'Connected' : 'Disconnected'),
                            ))
                        .toList(),
                  ),
          ),

          // ── Camera Stream ─────────────────────────────────────────────────
          _SectionCard(
            title: 'Camera Stream',
            child: Column(children: [
              _StatusRow('Stream state', _streamState.name),
              const SizedBox(height: 8),

              // Live camera preview
              AspectRatio(
                aspectRatio: 9 / 16,
                child: Container(
                  decoration: BoxDecoration(
                    color: Colors.black,
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: _currentFrame != null
                      ? ClipRRect(
                          borderRadius: BorderRadius.circular(12),
                          child: Image.memory(
                            _currentFrame!,
                            gaplessPlayback: true,
                            fit: BoxFit.cover,
                          ),
                        )
                      : const Center(
                          child: Text(
                            'No stream active',
                            style: TextStyle(color: Colors.grey),
                          ),
                        ),
                ),
              ),

              const SizedBox(height: 12),
              Row(children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _cameraStream == null &&
                            _registrationState == RegistrationState.registered
                        ? _startStreaming
                        : null,
                    icon: const Icon(Icons.videocam),
                    label: const Text('Start Stream'),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton.icon(
                  onPressed: _cameraStream != null ? _stopStreaming : null,
                  icon: const Icon(Icons.stop),
                  label: const Text('Stop'),
                ),
              ]),
            ]),
          ),

          // ── Photo Capture ─────────────────────────────────────────────────
          _SectionCard(
            title: 'Photo Capture',
            child: Column(children: [
              ElevatedButton.icon(
                onPressed: _cameraStream != null ? _capturePhoto : null,
                icon: const Icon(Icons.camera_alt),
                label: const Text('Capture Photo'),
              ),
              if (_lastPhoto != null) ...[
                const SizedBox(height: 12),
                ClipRRect(
                  borderRadius: BorderRadius.circular(8),
                  child: Image.memory(_lastPhoto!.bytes),
                ),
                Text(
                  'Captured at ${DateTime.fromMillisecondsSinceEpoch(_lastPhoto!.timestampMs)}',
                  style: Theme.of(context).textTheme.bodySmall,
                ),
              ],
            ]),
          ),

          // ── Display Integration ───────────────────────────────────────────
          _SectionCard(
            title: 'Display Integration',
            child: Column(children: [
              _StatusRow('Display Status', _displayId != null ? 'Active' : 'Inactive'),
              const SizedBox(height: 8),
              Row(children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _displayId == null && _registrationState == RegistrationState.registered
                        ? _showDisplayScreen
                        : null,
                    icon: const Icon(Icons.monitor),
                    label: const Text('Show Display'),
                  ),
                ),
                const SizedBox(width: 8),
                ElevatedButton.icon(
                  onPressed: _displayId != null ? _stopDisplay : null,
                  icon: const Icon(Icons.close),
                  label: const Text('Stop'),
                ),
              ]),
            ]),
          ),

          // ── Audio Routing ────────────────────────────────────────────────
          _SectionCard(
            title: 'Audio Routing',
            child: Column(children: [
              const Text('HFP provides microphone, A2DP provides HQ audio out.'),
              const SizedBox(height: 8),
              Row(children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _routeMicHFP,
                    icon: const Icon(Icons.mic),
                    label: const Text('Route HFP (Mic)'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                   child: ElevatedButton.icon(
                     onPressed: _routeA2DP,
                     icon: const Icon(Icons.speaker),
                     label: const Text('Route A2DP (HQ)'),
                   ),
                ),
              ]),
            ]),
          ),
        ],
      ),
    );
  }
}

// ── Small reusable widgets ────────────────────────────────────────────────────

class _SectionCard extends StatelessWidget {
  final String title;
  final Widget child;
  const _SectionCard({required this.title, required this.child});

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
          Text(title,
              style: Theme.of(context)
                  .textTheme
                  .titleMedium
                  ?.copyWith(fontWeight: FontWeight.bold)),
          const Divider(),
          child,
        ]),
      ),
    );
  }
}

class _StatusRow extends StatelessWidget {
  final String label;
  final String value;
  const _StatusRow(this.label, this.value);

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(label, style: const TextStyle(color: Colors.grey)),
        Text(value, style: const TextStyle(fontWeight: FontWeight.w500)),
      ],
    );
  }
}
0
likes
150
points
13
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for Meta Wearables Device Access Toolkit (MWDAT).

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on meta_wearables_flutter

Packages that implement meta_wearables_flutter