diff --git a/lib/services/audio_player/custom_player.dart b/lib/services/audio_player/custom_player.dart index f0dc8f13..34e6f917 100644 --- a/lib/services/audio_player/custom_player.dart +++ b/lib/services/audio_player/custom_player.dart @@ -4,43 +4,52 @@ 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 _playerStateStream; - final StreamController _shuffleStream; + final StreamController _playerStateStream = StreamController.broadcast(); + final StreamController _shuffleStream = StreamController.broadcast(); late final List _subscriptions; - - bool _shuffled; + bool _shuffled = false; int _androidAudioSessionId = 0; String _packageName = ""; AndroidAudioManager? _androidAudioManager; - CustomPlayer({super.configuration}) - : _playerStateStream = StreamController.broadcast(), - _shuffleStream = StreamController.broadcast(), - _shuffled = false { + CustomPlayer({super.configuration}) { nativePlayer.setProperty("network-timeout", "120"); + _initPlatformSpecificSetup(); + _listenToPlayerEvents(); + } + Future _initPlatformSpecificSetup() async { + final packageInfo = await PackageInfo.fromPlatform(); + _packageName = packageInfo.packageName; + + if (kIsAndroid) { + _androidAudioManager = AndroidAudioManager(); + _androidAudioSessionId = await _androidAudioManager!.generateAudioSessionId(); + notifyAudioSessionUpdate(true); + await _setAndroidAudioSession(); + } + } + + Future _setAndroidAudioSession() async { + await nativePlayer.setProperty("audiotrack-session-id", _androidAudioSessionId.toString()); + await nativePlayer.setProperty("ao", "audiotrack,opensles,"); + } + + void _listenToPlayerEvents() { _subscriptions = [ - stream.buffering.listen((event) { - _playerStateStream.add(AudioPlaybackState.buffering); - }), + stream.buffering.listen((_) => _playerStateStream.add(AudioPlaybackState.buffering)), stream.playing.listen((playing) { - if (playing) { - _playerStateStream.add(AudioPlaybackState.playing); - } else { - _playerStateStream.add(AudioPlaybackState.paused); - } + _playerStateStream.add(playing ? AudioPlaybackState.playing : AudioPlaybackState.paused); }), - stream.completed.listen((isCompleted) async { - if (!isCompleted) return; - _playerStateStream.add(AudioPlaybackState.completed); + stream.completed.listen((isCompleted) { + if (isCompleted) { + _playerStateStream.add(AudioPlaybackState.completed); + } }), stream.playlist.listen((event) { if (event.medias.isEmpty) { @@ -51,23 +60,6 @@ class CustomPlayer extends Player { 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 notifyAudioSessionUpdate(bool active) async { @@ -79,7 +71,7 @@ class CustomPlayer extends Player { : "android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION", data: { "android.media.extra.AUDIO_SESSION": _androidAudioSessionId, - "android.media.extra.PACKAGE_NAME": _packageName + "android.media.extra.PACKAGE_NAME": _packageName, }, ), ); @@ -90,6 +82,7 @@ class CustomPlayer extends Player { Stream get playerStateStream => _playerStateStream.stream; Stream get shuffleStream => _shuffleStream.stream; + Stream get indexChangeStream { int oldIndex = state.playlist.index; return stream.playlist.map((event) => event.index).where((newIndex) { @@ -106,6 +99,8 @@ class CustomPlayer extends Player { _shuffled = shuffle; await super.setShuffle(shuffle); _shuffleStream.add(shuffle); + + // Ensure delay before rearranging playlist await Future.delayed(const Duration(milliseconds: 100)); if (shuffle) { await move(state.playlist.index, 0); @@ -115,7 +110,6 @@ class CustomPlayer extends Player { @override Future stop() async { await super.stop(); - _shuffled = false; _playerStateStream.add(AudioPlaybackState.stopped); _shuffleStream.add(false); @@ -123,10 +117,10 @@ class CustomPlayer extends Player { @override Future dispose() async { - for (var element in _subscriptions) { - element.cancel(); - } + await Future.wait(_subscriptions.map((sub) => sub.cancel())); await notifyAudioSessionUpdate(false); + await _playerStateStream.close(); + await _shuffleStream.close(); return super.dispose(); } @@ -138,10 +132,9 @@ class CustomPlayer extends Player { } Future 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', ''); - } + await nativePlayer.setProperty( + 'af', + normalize ? 'dynaudnorm=g=5:f=250:r=0.9:p=0.5' : '' + ); } }