audio_device_detection 1.0.1 copy "audio_device_detection: ^1.0.1" to clipboard
audio_device_detection: ^1.0.1 copied to clipboard

A Flutter plugin to detect changes in audio output devices like Bluetooth, wired headsets, and built-in speakers, and to get a list of currently connected devices.

example/lib/main.dart

import 'dart:async';
import 'dart:io'; // Added to check Platform.isAndroid
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

// Import the main file and models from the plugin we are developing.
import 'package:audio_device_detection/audio_device_detection.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Audio Device Detection Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // State variables to store the device list and update the UI.
  List<AudioDevice> _connectedDevices = [];
  bool _isLoading = false;
  String _permissionStatus = 'Please check the permission status.';

  // Variable to manage the event stream subscription.
  StreamSubscription<AudioDevice>? _deviceStateSubscription;

  // [Key] Variable for the Future Queue.
  // Initialized with a completed Future to allow immediate execution.
  Future<void> _fetchQueue = Future.value();

  @override
  void initState() {
    super.initState();
    // Check the current permission status when the app starts.
    _checkInitialPermission();
  }

  @override
  void dispose() {
    // It's crucial to cancel the stream subscription when the widget is disposed to prevent memory leaks.
    _deviceStateSubscription?.cancel();
    super.dispose();
  }

  /// Checks the current permission status at app startup and loads the device list if permission is granted.
  Future<void> _checkInitialPermission() async {
    if (Platform.isAndroid) {
      final status = await Permission.bluetoothConnect.status;
      if (status.isGranted) {
        setState(() {
          _permissionStatus = 'Bluetooth permission has been granted.';
        });
        // If permission is already granted, load the device list and register the event listener.
        _initializePlugin();
      } else {
        setState(() {
          _permissionStatus = 'Bluetooth permission needs to be requested.';
        });
      }
    } else {
      // iOS does not require a separate permission request, so proceed with initialization.
      setState(() {
        _permissionStatus = 'Ready to detect devices on iOS.';
      });
      _initializePlugin();
    }
  }

  /// A function to request Bluetooth connection permission.
  Future<void> _requestBluetoothPermission() async {
    if (!Platform.isAndroid) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('No separate permission request is needed on iOS.')),
        );
      }
      _initializePlugin();
      return;
    }

    final status = await Permission.bluetoothConnect.request();

    if (status.isGranted) {
      setState(() {
        _permissionStatus = 'Bluetooth permission has been granted.';
      });
      // On successful permission grant, load the device list and register the event listener.
      _initializePlugin();
    } else if (status.isDenied) {
      setState(() {
        _permissionStatus = 'Permission denied. Feature usage is limited.';
      });
    } else if (status.isPermanentlyDenied) {
      setState(() {
        _permissionStatus = 'Permission permanently denied. You must grant it from the app settings.';
      });
      // Provide an option to navigate to the app settings.
      openAppSettings();
    }
  }

  /// Initializes the plugin's features (device list, event stream).
  void _initializePlugin() {
    // Prevent duplicate subscriptions.
    if (_deviceStateSubscription != null) return;

    // 1. Fetch the initial list of connected devices by scheduling it in the queue.
    _scheduleFetchDevices();

    // 2. Subscribe to the device connection/disconnection event stream.
    _deviceStateSubscription = AudioDeviceDetection.instance.onDeviceStateChanged.listen(
          (AudioDevice device) {
        // Show a SnackBar when an event occurs.
        final message = device.isConnected
            ? '${device.name} has been connected. (${device.protocol.name})'
            : '${device.name} has been disconnected.';

        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text(message),
              duration: const Duration(seconds: 2),
            ),
          );
        }

        // Schedule a device list refresh to update the UI.
        _scheduleFetchDevices();
      },
      onError: (error) {
        print('Event stream error: $error');
      },
    );
  }

  /// Future Queue Implementation.
  /// Chains the _fetchConnectedDevices call to run after the previous task is complete.
  void _scheduleFetchDevices() {
    // Chains a new task to the queue and updates the _fetchQueue pointer.
    _fetchQueue = _fetchQueue.whenComplete(() async {
      // Give the OS some time to update the device list.
      // A delay of 500ms is generally safe for Bluetooth profile connections to settle.
      await Future.delayed(const Duration(milliseconds: 500));

      if (mounted) {
        await _fetchConnectedDevices();
      }
    });
  }

  /// Fetches the list of connected devices and updates the state.
  Future<void> _fetchConnectedDevices() async {
    print('_fetchConnectedDevices called');
    if (!mounted) return;
    setState(() {
      _isLoading = true;
    });
    try {
      final devices = await AudioDeviceDetection.instance.getConnectedDevices();
      print('_fetchConnectedDevices found: ${devices.length}');
      if (mounted) {
        setState(() {
          _connectedDevices = devices;
        });
      }
    } catch (e) {
      print('Failed to get device list: $e');
    } finally {
      if (mounted) {
        setState(() {
          _isLoading = false;
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Audio Device Detection Example'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Permission request button
            ElevatedButton.icon(
              icon: const Icon(Icons.bluetooth_searching),
              label: const Text('Request Permission & Initialize'),
              onPressed: _requestBluetoothPermission,
            ),
            const SizedBox(height: 16),
            // Display current permission status
            Text(
              _permissionStatus,
              style: Theme.of(context).textTheme.bodySmall,
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 8),
            const Divider(),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              child: Text(
                'Currently Connected Audio Devices',
                style: Theme.of(context).textTheme.titleMedium,
              ),
            ),
            // ListView to display the device list
            Expanded(
              child: _isLoading
                  ? const Center(child: CircularProgressIndicator())
                  : _connectedDevices.isEmpty
                  ? const Center(child: Text('No connected devices found.'))
                  : RefreshIndicator(
                onRefresh: _fetchConnectedDevices,
                child: ListView.builder(
                  itemCount: _connectedDevices.length,
                  itemBuilder: (context, index) {
                    final device = _connectedDevices[index];
                    return Card(
                      child: ListTile(
                        leading: const Icon(Icons.headset, color: Colors.blue),
                        title: Text(device.name),
                        subtitle: Text(
                          'Protocol: ${device.protocol.name.toUpperCase()} / ID: ${device.address ?? 'N/A'}',
                        ),
                      ),
                    );
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
2
likes
150
points
215
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin to detect changes in audio output devices like Bluetooth, wired headsets, and built-in speakers, and to get a list of currently connected devices.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on audio_device_detection

Packages that implement audio_device_detection