adhoc_plugin 2.0.4 copy "adhoc_plugin: ^2.0.4" to clipboard
adhoc_plugin: ^2.0.4 copied to clipboard

PlatformAndroid

Flutter plugin that allows creating and managing a mobile (Android only) ad hoc network

example/lib/main.dart

import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'dart:typed_data';

import 'package:adhoc_plugin/adhoc_plugin.dart';
import 'package:analyzer_plugin/utilities/pair.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';

import 'search_bar.dart';


void main() => runApp(AdHocMusicClient());

enum MenuOptions { add, search, display }

const platform = MethodChannel('adhoc.music.player/main');

class AdHocMusicClient extends StatefulWidget {
  @override
  _AdHocMusicClientState createState() => _AdHocMusicClientState();
}

class _AdHocMusicClientState extends State<AdHocMusicClient> {
  static const PLAYLIST = 0;
  static const REQUEST = 1;
  static const REPLY = 2;
  static const TRANSFER = 3;

  static const NONE = 'none';

  final TransferManager _manager = TransferManager(true);
  final List<AdHocDevice> _discovered = List.empty(growable: true);
  final List<AdHocDevice> _peers = List.empty(growable: true);
  final List<Pair<String, String>> _playlist = List.empty(growable: true);
  final HashMap<String, HashMap<String, PlatformFile?>> _globalPlaylist = HashMap();
  final HashMap<String, PlatformFile?> _localPlaylist = HashMap();
  final HashMap<String, bool> _isTransfering = HashMap();
  final Set<String> timestamps = <String>{};

  bool _requested = false;
  bool _display = false;
  String? _selected = NONE;

  @override
  void initState() {
    super.initState();
    _manager.enableBle(3600);
    _manager.eventStream.listen(_processAdHocEvent);
    _manager.open = true;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Builder(
        builder: (context) => Scaffold(
          appBar: AppBar(
            centerTitle: true,
            title: const Text('Ad Hoc Music Client'),
            actions: <Widget>[
              PopupMenuButton<MenuOptions>(
                onSelected: (result) async {
                  switch (result) {
                    case MenuOptions.add:
                      await _openFileExplorer();
                      break;

                    case MenuOptions.search:
                      var songs = List<String>.empty(growable: true);
                      _localPlaylist.entries.map((entry) => songs.add(entry.key));

                      _selected = (await showSearch(
                        context: context,
                        delegate: SearchBar(songs),
                      ))!;

                      if (_selected == null) {
                        _selected = NONE;
                      }
                      break;

                    case MenuOptions.display:
                      setState(() => _display = !_display);
                      break;
                  }
                },
                itemBuilder: (context) => <PopupMenuEntry<MenuOptions>>[
                  const PopupMenuItem<MenuOptions>(
                    value: MenuOptions.add,
                    child: ListTile(
                      leading: Icon(Icons.playlist_add),
                      title: Text('Add song to playlist'),
                    ),
                  ),
                  const PopupMenuItem<MenuOptions>(
                    value: MenuOptions.search,
                    child: ListTile(
                      leading: Icon(Icons.search),
                      title: Text('Search song'),
                    ),
                  ),
                  const PopupMenuItem<MenuOptions>(
                    value: MenuOptions.display,
                    child: ListTile(
                      leading: Icon(Icons.music_note),
                      title: Text('Switch view'),
                    ),
                  ),
                ],
              ),
            ],
          ),
          body: Column(
            mainAxisSize: MainAxisSize.min,
            children: <Widget>[
              Expanded(
                child: Column(
                  children: <Widget>[
                    if (!_display) ...<Widget>[
                      Card(child: ListTile(title: Center(child: Text('Ad Hoc Peers')))),

                      ElevatedButton(
                        child: Center(child: Text('Search for nearby devices')),
                        onPressed: _manager.discovery,
                      ),

                      Expanded(
                        child: ListView(
                          children: _discovered.map((device) {
                            var type = device.mac.ble == '' ? 'Wi-Fi' : 'BLE';
                            var mac = device.mac.ble == '' ? device.mac.wifi : device.mac.ble;
                            if (device.mac.ble != '' && device.mac.wifi != '') {
                              type = 'Wi-Fi/BLE';
                              mac = '${device.mac.wifi}/${device.mac.ble}';
                            }

                            return Card(
                              child: ListTile(
                                title: Center(child: Text(device.name!)),
                                subtitle: Center(child: Text('$type: $mac')),
                                onTap: () async {
                                  await _manager.connect(device);
                                  setState(() => _discovered.removeWhere((element) => (element.mac == device.mac)));
                                },
                              ),
                            );
                          }).toList(),
                        ),
                      ),
                    ] else ...<Widget>[
                      Card(child: Stack(
                        children: <Widget> [
                          ListTile(
                            title: Center(child: Text('$_selected')),
                            subtitle: Row(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: <Widget>[
                                IconButton(
                                  icon: Icon(Icons.play_arrow_rounded),
                                  onPressed: _play,
                                ),
                                IconButton(
                                  icon: Icon(Icons.pause_rounded),
                                  onPressed: _pause,
                                ),
                                IconButton(
                                  icon: Icon(Icons.stop_rounded),
                                  onPressed: _stop,
                                ),
                                if (_requested)
                                  Container(child: Center(child: CircularProgressIndicator()))
                                else
                                  Container()
                              ],
                            ),
                          ),
                        ],
                      )),

                      Card(
                        color: Colors.blue,
                        child: ListTile(
                          title: Center(
                            child: const Text('Ad Hoc Playlist', style: TextStyle(color: Colors.white)),
                          ),
                        ),
                      ),

                      Expanded(
                        child: ListView(
                          children: _playlist.map((pair) {
                            return Card(
                              child: ListTile(
                                title: Center(child: Text(pair.last)),
                                subtitle: Center(child: Text(pair.first)),
                                onTap: () => setState(() => _selected = pair.last),
                              ),
                            );
                          }).toList(),
                        ),
                      ),
                    ],
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  void _processAdHocEvent(Event event) {
    switch (event.type) {
      case AdHocType.onDeviceDiscovered:
        break;
      case AdHocType.onDiscoveryStarted:
        break;
      case AdHocType.onDiscoveryCompleted:
        setState(() {
          for (final discovered in (event.data as Map).values) {
            _discovered.add(discovered as AdHocDevice);
          }
        });
        break;
      case AdHocType.onDataReceived:
        _processDataReceived(event);
        break;
      case AdHocType.onForwardData:
        _processDataReceived(event);
        break;
      case AdHocType.onConnection:
        _peers.add(event.device as AdHocDevice);
        break;
      case AdHocType.onConnectionClosed:
        break;
      case AdHocType.onInternalException:
        break;
      case AdHocType.onGroupInfo:
        break;
      case AdHocType.onGroupDataReceived:
        break;
      default:
    }
  }

  Future<void> _processDataReceived(Event event) async {
    var peer = event.device;
    var data = event.data as Map;

    switch (data['type'] as int) {
      case PLAYLIST:
        var peers = data['peers'] as List;
        var songs = data['songs'] as List;
        var timestamp = data['timestamp'] as String;
        if (timestamps.contains(timestamp)) {
          break;
        } else {
          timestamps.add(timestamp);
        }

        var peerName = peers.first as String;
        var entry = _globalPlaylist[peerName];
        if (entry == null) {
          entry = HashMap();
        }

        for (var i = 0; i < peers.length; i++) {
          if (peerName == peers[i]) {
            entry!.putIfAbsent(songs[i] as String, () => PlatformFile(name: songs[i] as String, size: 0));
          } else {
            _globalPlaylist[peerName] = entry!;

            peerName = peers[i] as String;
            entry = _globalPlaylist[peerName];
            if (entry == null) {
              entry = HashMap();
            }

            entry.putIfAbsent(songs[i] as String, () => PlatformFile(name: songs[i] as String, size: 0));
          }

          var pair = Pair<String, String>(peerName, songs[i] as String);
          if (!_playlist.contains(pair)) {
            _playlist.add(pair);
          }
        }

        _globalPlaylist[peerName] = entry!;

        setState(() {});

        _manager.broadcastExcept(data, peer!);
        break;

      case REQUEST:
        var name = data['name'] as String;
        var found = false;
        Uint8List? bytes;
        PlatformFile? file;

        if (_localPlaylist.containsKey(name)) {
          found = true;
          bytes = _localPlaylist[name]!.bytes!;
        } else {
          for (final entry in _globalPlaylist.entries) {
            var _playlist = entry.value;
            if (_playlist.containsKey(name)) {
              file = _playlist[name];
              if (file == null && file!.bytes == null) {
                found = false;
                break;
              } else {
                bytes = file.bytes;
                if (bytes != null) {
                  found = true;
                } else {
                  found = false;
                }
                break;
              }
            }
          }
        }

        if (found == false) {
          break;
        } else {
          var message = HashMap<String, dynamic>();
          message = HashMap<String, dynamic>();
          message.putIfAbsent('type', () => TRANSFER);
          message.putIfAbsent('name', () => name);
          _manager.sendMessageTo(message, peer!.label!);

          message.clear();

          message.putIfAbsent('type', () => REPLY);
          message.putIfAbsent('name', () => name);
          message.putIfAbsent('song', () => bytes);
          _manager.sendMessageTo(message, peer.label!);
        }

        break;

      case REPLY:
        var name = data['name'] as String;
        var song = Uint8List.fromList((data['song'] as List<dynamic>).cast<int>());

        var tempDir = await getTemporaryDirectory();
        var tempFile = File('${tempDir.path}/$name');
        await tempFile.writeAsBytes(song, flush: true);

        var entry = HashMap<String, PlatformFile>();
        entry.putIfAbsent(
          name, () => PlatformFile(
            bytes: song, name: name, path: tempFile.path, size: song.length
          )
        );

        _globalPlaylist.update(peer!.label!, (value) => entry, ifAbsent: () => entry);
        setState(() => _requested = false);
        break;

      case TRANSFER:
        var name = data['name'] as String;
        _isTransfering.update(name, (value) => true, ifAbsent: () => true);
        break;

      default:
    }
  }

  Future<void> _openFileExplorer() async {
    var result = await FilePicker.platform.pickFiles(
      allowMultiple: true,
      type: FileType.audio,
    );

    if(result != null) {
      for (var file in result.files) {
        var bytes = await File(file.path!).readAsBytes();
        var song = PlatformFile(
          name: file.name,
          path: file.path,
          bytes: bytes, 
          size: bytes.length,
        );

        _localPlaylist.putIfAbsent(file.name, () => song);
        var pair = Pair<String, String>(_manager.ownAddress, file.name);
        if (!_playlist.contains(pair)) {
          _playlist.add(pair);
        }
      }
    }

    _updatePlaylist();
  }

  void _updatePlaylist() async {
    var peers = List<String>.empty(growable: true);
    var songs = List<String>.empty(growable: true);

    _globalPlaylist.forEach((peer, song) {
      peers.add(peer);
      song.forEach((key, value) {
        songs.add(key);
      });
    });

    _localPlaylist.forEach((name, file) {
      peers.add(_manager.ownAddress);
      songs.add(name);
    });

    var message = HashMap<String, dynamic>();
    message.putIfAbsent('type', () => PLAYLIST);
    message.putIfAbsent('peers', () => peers);
    message.putIfAbsent('songs', () => songs);
    message.putIfAbsent('timestamp', () => DateTime.now().toIso8601String());
    _manager.broadcast(message);
  }

  void _play() {
    if (_selected!.compareTo(NONE) == 0) {
      return;
    }

    PlatformFile? file;
    if (_localPlaylist.containsKey(_selected)) {
      file = _localPlaylist[_selected];
    } else {
      _globalPlaylist.forEach((peerName, playlist) {
        if (playlist.containsKey(_selected)) {
          file = playlist[_selected];
          if (file == null || file!.size == 0) {
            var message = HashMap<String, dynamic>();
            message.putIfAbsent('type', () => REQUEST);
            message.putIfAbsent('name', () => _selected);
            _manager.broadcast(message);

            setState(() => _requested = true);
            _isTransfering.putIfAbsent(_selected!, () => false);

            Timer(Duration(seconds: 30), () {
              if (_requested == true && _isTransfering[_selected] == false) {
                _manager.sendMessageTo(message, peerName);
              }
            });
          }
        }
      });
    }

    if (_requested == false) {
      platform.invokeMethod('play', file!.path);
    }
  }

  void _pause() {
    if (_selected!.compareTo(NONE) == 0) {
      return;
    }

    platform.invokeMethod('pause');
  }

  void _stop() {
    if (_selected!.compareTo(NONE) == 0) {
      return;
    }

    _selected = NONE;
    platform.invokeMethod('stop');

    setState(() {});
  }
}
1
likes
130
points
43
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter plugin that allows creating and managing a mobile (Android only) ad hoc network

Repository (GitHub)
View/report issues

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

cryptography, flutter, flutter_reactive_ble, json_annotation, ninja_prime, pointycastle, uuid

More

Packages that depend on adhoc_plugin

Packages that implement adhoc_plugin