import 'dart:async'; import 'package:audio_service/audio_service.dart'; import '../../domain/entities/playback_state.dart' as entity; import '../models/playback_state_model.dart'; import '../models/song_model.dart'; import 'audio_manager_contract.dart'; import 'audio_player_task.dart'; typedef Conversion = T Function(S); // index geht verloren, wenn noch kein Subscriber vorhanden ist, weil die events nicht gebuffert werden (kann ich nicht ändern) // deshalb sollte sofort ein listener erstellt werden, der den jeweils letzten wert speichert // der fertige Stream nimmt dann diesen letzten Wert, wenn er keinen im source stream findet // so sollte kein memory leak entstehen, weil immer nur ein wert gebuffert wird class AudioManagerImpl implements AudioManager { AudioManagerImpl() { AudioService.customEventStream.listen((event) { final data = event as Map; if (data.containsKey(KEY_INDEX)) { _queueIndex = data[KEY_INDEX] as int; } }); } final Stream _currentMediaItemStream = AudioService.currentMediaItemStream; final Stream _sourcePlaybackStateStream = AudioService.playbackStateStream; final Stream> _queue = AudioService.queueStream; @override final Stream customEventStream = AudioService.customEventStream; int _queueIndex; @override Stream get currentSongStream => _filterStream( _currentMediaItemStream, (MediaItem mi) => SongModel.fromMediaItem(mi), ); @override Stream get playbackStateStream => _filterStream( _sourcePlaybackStateStream, (PlaybackState ps) => PlaybackStateModel.fromASPlaybackState(ps), ); // TODO: test @override Stream> get queueStream { return _queue.map((mediaItems) => mediaItems.map((m) => SongModel.fromMediaItem(m)).toList()); } @override Stream get queueIndexStream => _queueIndexStream(AudioService.customEventStream.cast()); // TODO: test Stream _queueIndexStream(Stream> source) async* { if (_queueIndex != null) { yield _queueIndex; } await for (final data in source) { if (data.containsKey(KEY_INDEX)) { yield data[KEY_INDEX] as int; } } } @override Stream get currentPositionStream => _position().distinct(); @override Future playSong(int index, List songList) async { await _startAudioService(); final List queue = songList.map((s) => s.path).toList(); await AudioService.customAction(PLAY_WITH_CONTEXT, [queue, index]); } @override Future play() async { await AudioService.play(); } @override Future pause() async { await AudioService.pause(); } Future _startAudioService() async { if (!AudioService.running) { await AudioService.start( backgroundTaskEntrypoint: _backgroundTaskEntrypoint, androidEnableQueue: true, ); await AudioService.customAction(INIT); } } @override Future skipToNext() async { await AudioService.skipToNext(); } @override Future skipToPrevious() async { await AudioService.skipToPrevious(); } Stream _filterStream(Stream stream, Conversion fn) async* { T lastItem; await for (final S item in stream) { final T newItem = fn(item); if (newItem != lastItem) { lastItem = newItem; yield newItem; } } } Stream _position() async* { PlaybackState state; Duration updateTime; Duration statePosition; // should this class get an init method for this? _sourcePlaybackStateStream.listen((currentState) { state = currentState; updateTime = currentState?.updateTime; statePosition = currentState?.position; }); while (true) { if (statePosition != null && updateTime != null && state != null) { if (state.playing) { yield statePosition.inMilliseconds + (DateTime.now().millisecondsSinceEpoch - updateTime.inMilliseconds); } else { yield statePosition.inMilliseconds; } } else { yield 0; } await Future.delayed(const Duration(milliseconds: 200)); } } } void _backgroundTaskEntrypoint() { AudioServiceBackground.run(() => AudioPlayerTask()); }