150 lines
4.5 KiB
Dart
Executable File
150 lines
4.5 KiB
Dart
Executable File
import 'dart:async';
|
|
import 'package:get/get.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';
|
|
import 'package:rhythm_box/platform.dart';
|
|
import 'package:rhythm_box/providers/error_notifier.dart';
|
|
|
|
// ignore: implementation_imports
|
|
import 'package:rhythm_box/services/audio_player/playback_state.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;
|
|
final StreamController<bool> _shuffleStream;
|
|
|
|
late final List<StreamSubscription> _subscriptions;
|
|
|
|
bool _shuffled;
|
|
int _androidAudioSessionId = 0;
|
|
String _packageName = '';
|
|
AndroidAudioManager? _androidAudioManager;
|
|
|
|
CustomPlayer({super.configuration})
|
|
: _playerStateStream = StreamController.broadcast(),
|
|
_shuffleStream = StreamController.broadcast(),
|
|
_shuffled = false {
|
|
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) {
|
|
Get.find<ErrorNotifier>().logError('[Playback] Error: $event');
|
|
}),
|
|
];
|
|
PackageInfo.fromPlatform().then((packageInfo) {
|
|
_packageName = packageInfo.packageName;
|
|
});
|
|
if (PlatformInfo.isAndroid) {
|
|
_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 (PlatformInfo.isAndroid) {
|
|
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 => _shuffled;
|
|
|
|
Stream<AudioPlaybackState> get playerStateStream => _playerStateStream.stream;
|
|
Stream<bool> get shuffleStream => _shuffleStream.stream;
|
|
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 {
|
|
_shuffled = shuffle;
|
|
await super.setShuffle(shuffle);
|
|
_shuffleStream.add(shuffle);
|
|
await Future.delayed(const Duration(milliseconds: 100));
|
|
if (shuffle) {
|
|
await move(state.playlist.index, 0);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> stop() async {
|
|
await super.stop();
|
|
|
|
_shuffled = false;
|
|
_playerStateStream.add(AudioPlaybackState.stopped);
|
|
_shuffleStream.add(false);
|
|
}
|
|
|
|
@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 move(state.playlist.medias.length, 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', '');
|
|
}
|
|
}
|
|
}
|