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

A professional Flutter plugin to query local device music, manage playlists, and apply audio effects on Android and iOS.

example/lib/main.dart

import 'dart:io';

import 'package:aura_music_kit/aura_music_kit.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:rxdart/rxdart.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Aura Music Player',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      debugShowCheckedModeBanner: false,
      home: const MusicListScreen(), // Set your screen as the home
    );
  }
}


// this code for music_list_screen.dart

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

  @override
  State<MusicListScreen> createState() => _MusicListScreenState();
}

class _MusicListScreenState extends State<MusicListScreen> {
  List<Song> _songs = [];
  bool _isLoading = true;
  String _statusText = 'Checking permissions...';
  PermissionStatus? _permissionStatus;

  @override
  void initState() {
    super.initState();
    // FIX: Check the permission status first without requesting it.
    _checkPermissionStatus();
  }

  /// Checks the current permission status and updates the UI accordingly.
  Future<void> _checkPermissionStatus() async {
    final status = await _getPermission();
    setState(() {
      _permissionStatus = status;
    });

    if (status.isGranted) {
      _fetchMusic();
    } else {
      setState(() {
        _isLoading = false;
        _statusText = 'This app needs permission to access your music.';
      });
    }
  }

  /// Fetches music files after permission has been granted.
  Future<void> _fetchMusic() async {
    if (!mounted) return;
    setState(() {
      _isLoading = true;
      _statusText = 'Loading music...';
    });
    
    final songs = await AuraMusicKit.getMusicFiles();

    if (mounted) {
      setState(() {
        _songs = songs;
        _isLoading = false;
        if (_songs.isEmpty) {
          _statusText = 'No music found on this device.\nPull down to refresh.';
        }
      });
    }
  }

  /// Gets the appropriate permission based on the platform and Android version.
  Future<PermissionStatus> _getPermission() async {
    if (Platform.isAndroid) {
      final androidInfo = await DeviceInfoPlugin().androidInfo;
      if (androidInfo.version.sdkInt >= 33) {
        return await Permission.audio.status;
      } else {
        return await Permission.storage.status;
      }
    } else {
      return await Permission.mediaLibrary.status;
    }
  }
  
  /// Requests the appropriate permission when the user taps the button.
  Future<void> _requestPermission() async {
    PermissionStatus status;
     if (Platform.isAndroid) {
      final androidInfo = await DeviceInfoPlugin().androidInfo;
      if (androidInfo.version.sdkInt >= 33) {
        status = await Permission.audio.request();
      } else {
        status = await Permission.storage.request();
      }
    } else {
      status = await Permission.mediaLibrary.request();
    }

    setState(() {
      _permissionStatus = status;
    });

    if (status.isGranted) {
      _fetchMusic();
    }
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Aura Music Kit'),
        backgroundColor: Colors.deepPurple.shade300,
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    if (_permissionStatus == null || _isLoading) {
      return Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const CircularProgressIndicator(),
            const SizedBox(height: 16),
            Text(_statusText),
          ],
        ),
      );
    }

    if (!_permissionStatus!.isGranted) {
      return _buildPermissionRequestView();
    }

    return RefreshIndicator(
      onRefresh: _fetchMusic,
      child: _songs.isEmpty ? _buildEmptyView() : _buildSongList(),
    );
  }

  /// The view shown when permission has not been granted.
  Widget _buildPermissionRequestView() {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(24.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.folder_off, size: 80, color: Colors.grey.shade400),
            const SizedBox(height: 16),
            Text(
              _statusText,
              textAlign: TextAlign.center,
              style: TextStyle(fontSize: 16, color: Colors.grey.shade600),
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: _requestPermission,
              child: const Text('Grant Permission'),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildEmptyView() {
    return Center(
      child: ListView(
        shrinkWrap: true,
        children: [
          Center(
            child: Padding(
              padding: const EdgeInsets.all(24.0),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(Icons.music_off, size: 80, color: Colors.grey.shade400),
                  const SizedBox(height: 16),
                  Text(
                    _statusText,
                    textAlign: TextAlign.center,
                    style: TextStyle(fontSize: 16, color: Colors.grey.shade600),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildSongList() {
    return ListView.builder(
      itemCount: _songs.length,
      itemBuilder: (context, index) {
        final song = _songs[index];
        return ListTile(
          leading: ClipRRect(
            borderRadius: BorderRadius.circular(8.0),
            child: SizedBox(
              width: 50,
              height: 50,
              child: song.artwork != null
                  ? Image.memory(
                      song.artwork!,
                      fit: BoxFit.cover,
                      errorBuilder: (context, error, stackTrace) =>
                          const Icon(Icons.music_note, size: 30),
                    )
                  : const Icon(Icons.music_note, size: 30),
            ),
          ),
          title: Text(
            song.title,
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            style: const TextStyle(fontWeight: FontWeight.w500),
          ),
          subtitle: Text(
            song.artist ?? "Unknown Artist",
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          onTap: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => PlayerScreen(
                  songs: _songs,
                  initialIndex: index,
                ),
              ),
            );
          },
        );
      },
    );
  }
}


// this code for player_screen.dart


// Helper class is now simpler
class PositionData {
  final Duration position;
  final Duration duration;

  const PositionData(this.position, this.duration);
}

class PlayerScreen extends StatefulWidget {
  final List<Song> songs;
  final int initialIndex;

  const PlayerScreen({
    super.key,
    required this.songs,
    required this.initialIndex,
  });

  @override
  State<PlayerScreen> createState() => _PlayerScreenState();
}

class _PlayerScreenState extends State<PlayerScreen> {
  final _equalizer = AndroidEqualizer();
  late AudioPlayer _audioPlayer;
  late int _currentIndex;

  // MODIFIED: Combine just position and duration streams
  Stream<PositionData> get _positionDataStream =>
      Rx.combineLatest2<Duration, Duration?, PositionData>(
        _audioPlayer.positionStream,
        _audioPlayer.durationStream,
        (position, duration) => PositionData(
          position,
          duration ?? Duration.zero,
        ),
      );

  @override
  void initState() {
    super.initState();
    _currentIndex = widget.initialIndex;
    _audioPlayer = AudioPlayer(
      audioPipeline: AudioPipeline(
        androidAudioEffects: [_equalizer],
      ),
    );

    _loadSong();

    _audioPlayer.processingStateStream.listen((state) {
      if (state == ProcessingState.completed) {
        _playNext();
      }
    });
  }

  Future<void> _loadSong() async {
    final song = widget.songs[_currentIndex];
    try {
      await _audioPlayer.setAudioSource(AudioSource.uri(Uri.file(song.path!)));
      _audioPlayer.play();
    } catch (e) {
      debugPrint("Error loading song: $e");
    }
  }

  void _playNext() {
    if (_currentIndex < widget.songs.length - 1) {
      setState(() {
        _currentIndex++;
      });
      _loadSong();
    }
  }

  void _playPrevious() {
    if (_currentIndex > 0) {
      setState(() {
        _currentIndex--;
      });
      _loadSong();
    }
  }

  @override
  void dispose() {
    _audioPlayer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final currentSong = widget.songs[_currentIndex];
    return Scaffold(
      appBar: AppBar(
        title: const Text('Now Playing'),
        backgroundColor: Colors.deepPurple.shade300,
        actions: [
          IconButton(
            icon: const Icon(Icons.equalizer),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (_) => EqualizerScreen(equalizer: _equalizer),
                ),
              );
            },
          ),
        ],
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 24.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Spacer(),
            ClipRRect(
              borderRadius: BorderRadius.circular(16.0),
              child: SizedBox(
                width: 250,
                height: 250,
                child: currentSong.artwork != null
                    ? Image.memory(
                        currentSong.artwork!,
                        fit: BoxFit.cover,
                      )
                    : const Icon(Icons.music_note, size: 120),
              ),
            ),
            const SizedBox(height: 32),
            Text(
              currentSong.title,
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
            const SizedBox(height: 8),
            Text(
              currentSong.artist ?? "Unknown Artist",
              style: TextStyle(fontSize: 16, color: Colors.grey.shade600),
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
            ),
            const SizedBox(height: 24),
            _buildProgressBar(),
            const SizedBox(height: 16),
            _buildControls(),
            const Spacer(),
          ],
        ),
      ),
    );
  }

  Widget _buildProgressBar() {
    return StreamBuilder<PositionData>(
      stream: _positionDataStream,
      builder: (context, snapshot) {
        final positionData =
            snapshot.data ?? const PositionData(Duration.zero, Duration.zero);
        final position = positionData.position;
        final duration = positionData.duration;

        return Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Slider(
              min: 0.0,
              max: duration.inMilliseconds.toDouble(),
              value: position.inMilliseconds
                  .toDouble()
                  .clamp(0.0, duration.inMilliseconds.toDouble()),
              // REMOVED: secondaryTrackValue property
              onChangeEnd: (value) {
                _audioPlayer.seek(Duration(milliseconds: value.round()));
              },
              onChanged: (value) {
                 // No action needed while dragging, but callback is required.
              },
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text(_formatDuration(position)),
                Text(_formatDuration(duration)),
              ],
            ),
          ],
        );
      },
    );
  }

  Widget _buildControls() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        IconButton(
          icon: const Icon(Icons.skip_previous),
          iconSize: 40,
          onPressed: _playPrevious,
        ),
        const SizedBox(width: 20),
        StreamBuilder<PlayerState>(
          stream: _audioPlayer.playerStateStream,
          builder: (context, snapshot) {
            final playerState = snapshot.data;
            final playing = playerState?.playing ?? false;
            return IconButton(
              icon: Icon(playing
                  ? Icons.pause_circle_filled
                  : Icons.play_circle_filled),
              iconSize: 64,
              onPressed: () {
                if (playing) {
                  _audioPlayer.pause();
                } else {
                  _audioPlayer.play();
                }
              },
            );
          },
        ),
        const SizedBox(width: 20),
        IconButton(
          icon: const Icon(Icons.skip_next),
          iconSize: 40,
          onPressed: _playNext,
        ),
      ],
    );
  }

  String _formatDuration(Duration d) {
    final minutes = d.inMinutes.remainder(60).toString().padLeft(2, '0');
    final seconds = d.inSeconds.remainder(60).toString().padLeft(2, '0');
    return '$minutes:$seconds';
  }
}

// this code for equalizer_screen.dart


class EqualizerScreen extends StatefulWidget {
  // We require the AndroidEqualizer instance directly
  final AndroidEqualizer equalizer;

  const EqualizerScreen({
    super.key,
    required this.equalizer,
  });

  @override
  State<EqualizerScreen> createState() => _EqualizerScreenState();
}

class _EqualizerScreenState extends State<EqualizerScreen> {
  late bool _isEqualizerEnabled;

  @override
  void initState() {
    super.initState();
    // Initialize the equalizer state
    _isEqualizerEnabled = widget.equalizer.enabled;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Equalizer'),
        backgroundColor: Colors.deepPurple.shade300,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            SwitchListTile(
              title: const Text('Enable Equalizer'),
              value: _isEqualizerEnabled,
              onChanged: (value) {
                setState(() {
                  _isEqualizerEnabled = value;
                  widget.equalizer.setEnabled(value);
                });
              },
            ),
            const Divider(),
            Expanded(
              // Use a FutureBuilder to get the full parameters object
              child: FutureBuilder<AndroidEqualizerParameters>(
                // Get the whole 'parameters' object, not just the bands
                future: widget.equalizer.parameters,
                builder: (context, snapshot) {
                  if (snapshot.connectionState == ConnectionState.waiting) {
                    return const Center(child: CircularProgressIndicator());
                  }
                  if (snapshot.hasError || !snapshot.hasData) {
                    return const Center(child: Text('Error loading equalizer bands.'));
                  }
                  
                  // -- THIS IS THE FIX --
                  // Get the parameters from the snapshot
                  final parameters = snapshot.data!;
                  // Get the min and max decibel levels for the sliders
                  final minDb = parameters.minDecibels;
                  final maxDb = parameters.maxDecibels;
                  // Get the list of bands
                  final bands = parameters.bands;
                  // -- END OF FIX --

                  return ListView.builder(
                    itemCount: bands.length,
                    itemBuilder: (context, index) {
                      final band = bands[index];
                      return Column(
                        children: [
                          Text(
                            '${band.centerFrequency.round()} Hz',
                            style: const TextStyle(fontWeight: FontWeight.bold),
                          ),
                          StreamBuilder<double>(
                            stream: band.gainStream,
                            builder: (context, snapshot) {
                              return Slider(
                                // Use the min and max values from the parameters
                                min: minDb,
                                max: maxDb,
                                value: snapshot.data ?? band.gain,
                                onChanged: !_isEqualizerEnabled
                                    ? null
                                    : (value) {
                                        band.setGain(value);
                                      },
                              );
                            },
                          ),
                          StreamBuilder<double>(
                              stream: band.gainStream,
                              builder: (context, snapshot) {
                                return Text(
                                    '${(snapshot.data ?? band.gain).toStringAsFixed(2)} dB');
                              }),
                        ],
                      );
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
150
points
31
downloads

Publisher

unverified uploader

Weekly Downloads

A professional Flutter plugin to query local device music, manage playlists, and apply audio effects on Android and iOS.

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on aura_music_kit

Packages that implement aura_music_kit