content: import 'dart:async';
  import 'package:spotube/services/logger/logger.dart';
  import 'package:media_kit/media_kit.dart';
  import 'package:flutter_broadcasts/flutter_broadcasts.dart';
  import 'package:package_info_plus/package_info_plus.dart';
  import 'package:audio_session/audio_session.dart';
  // ignore: implementation_imports
  import 'package:spotube/services/audio_player/playback_state.dart';
  import 'package:spotube/utils/platform.dart';
  
  /// MediaKit [Player] by default doesn't have a state stream.
  /// This class adds a state stream to the [Player] class.
  class CustomPlayer extends Player {
    final StreamController<AudioPlaybackState> _playerStateStream;
  
    late final List<StreamSubscription> _subscriptions;
  
    int _androidAudioSessionId = 0;
    String _packageName = "";
    AndroidAudioManager? _androidAudioManager;
  
    CustomPlayer({super.configuration})
        : _playerStateStream = StreamController.broadcast() {
      nativePlayer.setProperty("network-timeout", "120");
  
      _subscriptions = [
        stream.buffering.listen((event) {
          _playerStateStream.add(AudioPlaybackState.buffering);
        }),
        stream.playing.listen((playing) {
          if (playing) {
            _playerStateStream.add(AudioPlaybackState.playing);
          } else {
            _playerStateStream.add(AudioPlaybackState.paused);
          }
        }),
        stream.completed.listen((isCompleted) async {
          if (!isCompleted) return;
          _playerStateStream.add(AudioPlaybackState.completed);
        }),
        stream.playlist.listen((event) {
          if (event.medias.isEmpty) {
            _playerStateStream.add(AudioPlaybackState.stopped);
          }
        }),
        stream.error.listen((event) {
          AppLogger.reportError('[MediaKitError] \n$event', StackTrace.current);
        }),
      ];
      PackageInfo.fromPlatform().then((packageInfo) {
        _packageName = packageInfo.packageName;
      });
      if (kIsAndroid) {
        _androidAudioManager = AndroidAudioManager();
        AudioSession.instance.then((s) async {
          _androidAudioSessionId =
              await _androidAudioManager!.generateAudioSessionId();
          notifyAudioSessionUpdate(true);
  
          await nativePlayer.setProperty(
            "audiotrack-session-id",
            _androidAudioSessionId.toString(),
          );
          await nativePlayer.setProperty("ao", "audiotrack,opensles,");
        });
      }
    }
  
    Future<void> notifyAudioSessionUpdate(bool active) async {
      if (kIsAndroid) {
        sendBroadcast(
          BroadcastMessage(
            name: active
                ? "android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION"
                : "android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION",
            data: {
              "android.media.extra.AUDIO_SESSION": _androidAudioSessionId,
              "android.media.extra.PACKAGE_NAME": _packageName
            },
          ),
        );
      }
    }
  
    bool get shuffled => state.shuffle;
  
    Stream<AudioPlaybackState> get playerStateStream => _playerStateStream.stream;
    Stream<bool> get shuffleStream => stream.shuffle;
    Stream<int> get indexChangeStream {
      int oldIndex = state.playlist.index;
      return stream.playlist.map((event) => event.index).where((newIndex) {
        if (newIndex != oldIndex) {
          oldIndex = newIndex;
          return true;
        }
        return false;
      });
    }
  
    @override
    Future<void> setShuffle(bool shuffle) async {
      await super.setShuffle(shuffle);
    }
  
    @override
    Future<void> stop() async {
      await super.stop();
  
      _playerStateStream.add(AudioPlaybackState.stopped);
    }
  
    @override
    Future<void> dispose() async {
      for (var element in _subscriptions) {
        element.cancel();
      }
      await notifyAudioSessionUpdate(false);
      return super.dispose();
    }
  
    NativePlayer get nativePlayer => platform as NativePlayer;
  
    Future<void> insert(int index, Media media) async {
      await add(media);
      await Future.delayed(const Duration(milliseconds: 100));
      await move(state.playlist.medias.length - 1, index);
    }
  
    Future<void> setAudioNormalization(bool normalize) async {
      if (normalize) {
        await nativePlayer.setProperty('af', 'dynaudnorm=g=5:f=250:r=0.9:p=0.5');
      } else {
        await nativePlayer.setProperty('af', '');
      }
    }
  }
  