content: // This is a minimal example demonstrating a play/pause button and a seek bar.
  // More advanced examples demonstrating other features can be found in the same
  // directory as this example in the GitHub repository.
  
  import 'media_kit_stub.dart' if (dart.library.io) 'media_kit_impl.dart';
  import 'package:audio_session/audio_session.dart';
  import 'package:flutter/material.dart';
  import 'package:flutter/services.dart';
  import 'package:just_audio/just_audio.dart';
  import 'package:just_audio_example/common.dart';
  import 'package:rxdart/rxdart.dart';
  
  void main() {
    initMediaKit(); // Initialise just_audio_media_kit for Linux/Windows.
    runApp(const MyApp());
  }
  
  class MyApp extends StatefulWidget {
    const MyApp({Key? key}) : super(key: key);
  
    @override
    MyAppState createState() => MyAppState();
  }
  
  class MyAppState extends State<MyApp> with WidgetsBindingObserver {
    final _player = AudioPlayer();
  
    @override
    void initState() {
      super.initState();
      ambiguate(WidgetsBinding.instance)!.addObserver(this);
      SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
        statusBarColor: Colors.black,
      ));
      _init();
    }
  
    Future<void> _init() async {
      // Inform the operating system of our app's audio attributes etc.
      // We pick a reasonable default for an app that plays speech.
      final session = await AudioSession.instance;
      await session.configure(const AudioSessionConfiguration.speech());
      // Listen to errors during playback.
      _player.errorStream.listen((e) {
        print('A stream error occurred: $e');
      });
      // Try to load audio from a source and catch any errors.
      try {
        // AAC example: https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.aac
        await _player.setAudioSource(AudioSource.uri(Uri.parse(
            "https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3")));
      } on PlayerException catch (e) {
        print("Error loading audio source: $e");
      }
    }
  
    @override
    void dispose() {
      ambiguate(WidgetsBinding.instance)!.removeObserver(this);
      // Release decoders and buffers back to the operating system making them
      // available for other apps to use.
      _player.dispose();
      super.dispose();
    }
  
    @override
    void didChangeAppLifecycleState(AppLifecycleState state) {
      if (state == AppLifecycleState.paused) {
        // Release the player's resources when not in use. We use "stop" so that
        // if the app resumes later, it will still remember what position to
        // resume from.
        _player.stop();
      }
    }
  
    /// Collects the data useful for displaying in a seek bar, using a handy
    /// feature of rx_dart to combine the 3 streams of interest into one.
    Stream<PositionData> get _positionDataStream =>
        Rx.combineLatest3<Duration, Duration, Duration?, PositionData>(
            _player.positionStream,
            _player.bufferedPositionStream,
            _player.durationStream,
            (position, bufferedPosition, duration) => PositionData(
                position, bufferedPosition, duration ?? Duration.zero));
  
    @override
    Widget build(BuildContext context) {
      return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: Scaffold(
          body: SafeArea(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // Display play/pause button and volume/speed sliders.
                ControlButtons(_player),
                // Display seek bar. Using StreamBuilder, this widget rebuilds
                // each time the position, buffered position or duration changes.
                StreamBuilder<PositionData>(
                  stream: _positionDataStream,
                  builder: (context, snapshot) {
                    final positionData = snapshot.data;
                    return SeekBar(
                      duration: positionData?.duration ?? Duration.zero,
                      position: positionData?.position ?? Duration.zero,
                      bufferedPosition:
                          positionData?.bufferedPosition ?? Duration.zero,
                      onChangeEnd: _player.seek,
                    );
                  },
                ),
              ],
            ),
          ),
        ),
      );
    }
  }
  
  /// Displays the play/pause button and volume/speed sliders.
  class ControlButtons extends StatelessWidget {
    final AudioPlayer player;
  
    const ControlButtons(this.player, {Key? key}) : super(key: key);
  
    @override
    Widget build(BuildContext context) {
      return Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          // Opens volume slider dialog
          IconButton(
            icon: const Icon(Icons.volume_up),
            onPressed: () {
              showSliderDialog(
                context: context,
                title: "Adjust volume",
                divisions: 10,
                min: 0.0,
                max: 1.0,
                value: player.volume,
                stream: player.volumeStream,
                onChanged: player.setVolume,
              );
            },
          ),
  
          /// This StreamBuilder rebuilds whenever the player state changes, which
          /// includes the playing/paused state and also the
          /// loading/buffering/ready state. Depending on the state we show the
          /// appropriate button or loading indicator.
          StreamBuilder<PlayerState>(
            stream: player.playerStateStream,
            builder: (context, snapshot) {
              final playerState = snapshot.data;
              final processingState = playerState?.processingState;
              final playing = playerState?.playing;
              if (processingState == ProcessingState.loading ||
                  processingState == ProcessingState.buffering) {
                return Container(
                  margin: const EdgeInsets.all(8.0),
                  width: 64.0,
                  height: 64.0,
                  child: const CircularProgressIndicator(),
                );
              } else if (playing != true) {
                return IconButton(
                  icon: const Icon(Icons.play_arrow),
                  iconSize: 64.0,
                  onPressed: player.play,
                );
              } else if (processingState != ProcessingState.completed) {
                return IconButton(
                  icon: const Icon(Icons.pause),
                  iconSize: 64.0,
                  onPressed: player.pause,
                );
              } else {
                return IconButton(
                  icon: const Icon(Icons.replay),
                  iconSize: 64.0,
                  onPressed: () => player.seek(Duration.zero),
                );
              }
            },
          ),
          IconButton(
            icon: const Icon(Icons.stop),
            iconSize: 64.0,
            onPressed: player.stop,
          ),
          // Opens speed slider dialog
          StreamBuilder<double>(
            stream: player.speedStream,
            builder: (context, snapshot) => IconButton(
              icon: Text("${snapshot.data?.toStringAsFixed(1)}x",
                  style: const TextStyle(fontWeight: FontWeight.bold)),
              onPressed: () {
                showSliderDialog(
                  context: context,
                  title: "Adjust speed",
                  divisions: 10,
                  min: 0.5,
                  max: 1.5,
                  value: player.speed,
                  stream: player.speedStream,
                  onChanged: player.setSpeed,
                );
              },
            ),
          ),
        ],
      );
    }
  }
  