implemented current position

This commit is contained in:
Moritz Weber 2020-04-11 20:23:02 +02:00
parent 3b7588831c
commit 494f2aa28b
11 changed files with 210 additions and 102 deletions

View file

@ -7,6 +7,7 @@ import '../entities/song.dart';
abstract class AudioRepository {
Stream<Song> get currentSongStream;
Stream<PlaybackState> get playbackStateStream;
Stream<int> get currentPositionStream;
Future<Either<Failure, void>> playSong(int index, List<Song> songList);
Future<Either<Failure, void>> play();

View file

@ -90,7 +90,7 @@ class CurrentlyPlayingPage extends StatelessWidget {
const Spacer(
flex: 3,
),
TimeProgressIndicator(),
const TimeProgressIndicator(),
const Spacer(
flex: 3,
),

View file

@ -32,12 +32,18 @@ abstract class _AudioStore with Store {
@observable
ObservableStream<PlaybackState> playbackStateStream;
@observable
ObservableStream<int> currentPositionStream;
@action
Future<void> init() async {
if (!_initialized) {
print('AudioStore.init');
currentSong = _audioRepository.currentSongStream.asObservable();
currentPositionStream =
_audioRepository.currentPositionStream.asObservable(initialValue: 0);
_disposers.add(autorun((_) {
updateSong(currentSong.value);
}));

View file

@ -63,6 +63,26 @@ mixin _$AudioStore on _AudioStore, Store {
name: '${_$playbackStateStreamAtom.name}_set');
}
final _$currentPositionStreamAtom =
Atom(name: '_AudioStore.currentPositionStream');
@override
ObservableStream<int> get currentPositionStream {
_$currentPositionStreamAtom.context
.enforceReadPolicy(_$currentPositionStreamAtom);
_$currentPositionStreamAtom.reportObserved();
return super.currentPositionStream;
}
@override
set currentPositionStream(ObservableStream<int> value) {
_$currentPositionStreamAtom.context.conditionallyRunInAction(() {
super.currentPositionStream = value;
_$currentPositionStreamAtom.reportChanged();
}, _$currentPositionStreamAtom,
name: '${_$currentPositionStreamAtom.name}_set');
}
final _$initAsyncAction = AsyncAction('init');
@override
@ -101,7 +121,7 @@ mixin _$AudioStore on _AudioStore, Store {
@override
String toString() {
final string =
'currentSong: ${currentSong.toString()},song: ${song.toString()},playbackStateStream: ${playbackStateStream.toString()}';
'currentSong: ${currentSong.toString()},song: ${song.toString()},playbackStateStream: ${playbackStateStream.toString()},currentPositionStream: ${currentPositionStream.toString()}';
return '{$string}';
}
}

View file

@ -10,3 +10,25 @@ ImageProvider getAlbumImage(String albumArtPath) {
}
return FileImage(File(albumArtPath));
}
String msToTimeString(int milliseconds) {
String twoDigits(num n) {
if (n >= 10) {
return '$n';
}
return '0$n';
}
final Duration duration = Duration(seconds: (milliseconds / 1000).round());
final int hours = duration.inHours;
final int minutes = duration.inMinutes.remainder(60) as int;
final String twoDigitMinutes = twoDigits(minutes);
final String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
if (hours > 0) {
return '$hours:$twoDigitMinutes:$twoDigitSeconds';
}
return '$minutes:$twoDigitSeconds';
}

View file

@ -23,8 +23,8 @@ class CurrentlyPlayingBar extends StatelessWidget {
return Column(
children: <Widget>[
Container(
child: const LinearProgressIndicator(
value: 0.42,
child: LinearProgressIndicator(
value: audioStore.currentPositionStream.value / audioStore.currentSong.value.duration,
),
height: 2,
),

View file

@ -1,35 +1,42 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
class TimeProgressIndicator extends StatefulWidget {
TimeProgressIndicator({Key key}) : super(key: key);
import '../state/audio_store.dart';
import '../utils.dart';
@override
_TimeProgressIndicatorState createState() => _TimeProgressIndicatorState();
}
class TimeProgressIndicator extends StatelessWidget {
const TimeProgressIndicator({Key key}) : super(key: key);
class _TimeProgressIndicatorState extends State<TimeProgressIndicator> {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
Text("0:42"),
Container(
width: 10,
final AudioStore audioStore = Provider.of<AudioStore>(context);
return Observer(
builder: (BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
Text(msToTimeString(audioStore.currentPositionStream.value)),
Container(
width: 10,
),
Expanded(
child: Container(
child: LinearProgressIndicator(value: audioStore.currentPositionStream.value / audioStore.currentSong.value.duration),
height: 3.0,
),
),
Container(
width: 10,
),
Text(msToTimeString(audioStore.currentSong.value.duration)),
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
Expanded(
child: Container(
child: LinearProgressIndicator(value: 0.42),
height: 3.0,
)),
Container(
width: 10,
),
Text("3:42"),
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
);
},
);
}
}

View file

@ -1,12 +1,12 @@
import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.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<S, T> = T Function(S);
@ -29,6 +29,9 @@ class AudioManagerImpl implements AudioManager {
(PlaybackState ps) => PlaybackStateModel.fromASPlaybackState(ps),
);
@override
Stream<int> get currentPositionStream => _position().distinct();
@override
Future<void> playSong(int index, List<SongModel> songList) async {
await _startAudioService();
@ -68,80 +71,35 @@ class AudioManagerImpl implements AudioManager {
}
}
}
Stream<int> _position() async* {
BasicPlaybackState state;
int updateTime;
int statePosition;
// should this class get an init method for this?
_sourcePlaybackStateStream.listen((currentState) {
state = currentState?.basicState;
updateTime = currentState?.updateTime;
statePosition = currentState?.position;
});
while (true) {
if (statePosition != null && updateTime != null && state != null) {
if (state == BasicPlaybackState.playing) {
yield statePosition +
(DateTime.now().millisecondsSinceEpoch - updateTime);
} else {
yield statePosition;
}
} else {
yield 0;
}
await Future.delayed(const Duration(milliseconds: 200));
}
}
}
void _backgroundTaskEntrypoint() {
AudioServiceBackground.run(() => AudioPlayerTask());
}
class AudioPlayerTask extends BackgroundAudioTask {
final _audioPlayer = AudioPlayer();
final _completer = Completer();
final _mediaItems = <String, MediaItem>{};
@override
Future<void> onStart() async {
// AudioServiceBackground.setState(
// controls: [],
// basicState: BasicPlaybackState.none,
// );
await _completer.future;
}
@override
void onStop() {
_audioPlayer.stop();
_completer.complete();
}
@override
void onAddQueueItem(MediaItem mediaItem) {
_mediaItems[mediaItem.id] = mediaItem;
}
@override
Future<void> onPlayFromMediaId(String mediaId) async {
AudioServiceBackground.setState(
controls: [pauseControl, stopControl],
basicState: BasicPlaybackState.playing,
);
await AudioServiceBackground.setMediaItem(_mediaItems[mediaId]);
await _audioPlayer.setFilePath(mediaId);
_audioPlayer.play();
}
@override
Future<void> onPlay() async {
AudioServiceBackground.setState(
controls: [pauseControl, stopControl],
basicState: BasicPlaybackState.playing);
_audioPlayer.play();
}
@override
Future<void> onPause() async {
AudioServiceBackground.setState(
controls: [playControl, stopControl],
basicState: BasicPlaybackState.paused);
await _audioPlayer.pause();
}
}
MediaControl playControl = const MediaControl(
androidIcon: 'drawable/ic_action_play_arrow',
label: 'Play',
action: MediaAction.play,
);
MediaControl pauseControl = const MediaControl(
androidIcon: 'drawable/ic_action_pause',
label: 'Pause',
action: MediaAction.pause,
);
MediaControl stopControl = const MediaControl(
androidIcon: 'drawable/ic_action_stop',
label: 'Stop',
action: MediaAction.stop,
);

View file

@ -4,6 +4,8 @@ import '../models/song_model.dart';
abstract class AudioManager {
Stream<SongModel> get currentSongStream;
Stream<PlaybackState> get playbackStateStream;
/// Current position in the song in milliseconds.
Stream<int> get currentPositionStream;
Future<void> playSong(int index, List<SongModel> songList);
Future<void> play();

View file

@ -0,0 +1,87 @@
import 'dart:async';
import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';
class AudioPlayerTask extends BackgroundAudioTask {
final _audioPlayer = AudioPlayer();
final _completer = Completer();
final _mediaItems = <String, MediaItem>{};
Duration _position;
@override
Future<void> onStart() async {
_audioPlayer.getPositionStream().listen((duration) => _position = duration);
// AudioServiceBackground.setState(
// controls: [],
// basicState: BasicPlaybackState.none,
// );
await _completer.future;
}
@override
void onStop() {
_audioPlayer.stop();
_completer.complete();
}
@override
void onAddQueueItem(MediaItem mediaItem) {
_mediaItems[mediaItem.id] = mediaItem;
}
@override
Future<void> onPlayFromMediaId(String mediaId) async {
AudioServiceBackground.setState(
controls: [pauseControl, stopControl],
basicState: BasicPlaybackState.playing,
updateTime: DateTime.now().millisecondsSinceEpoch,
);
await AudioServiceBackground.setMediaItem(_mediaItems[mediaId]);
await _audioPlayer.setFilePath(mediaId);
_audioPlayer.play();
}
@override
Future<void> onPlay() async {
AudioServiceBackground.setState(
controls: [pauseControl, stopControl],
basicState: BasicPlaybackState.playing,
updateTime: DateTime.now().millisecondsSinceEpoch,
position: _position.inMilliseconds,
);
_audioPlayer.play();
}
@override
Future<void> onPause() async {
AudioServiceBackground.setState(
controls: [playControl, stopControl],
basicState: BasicPlaybackState.paused,
updateTime: DateTime.now().millisecondsSinceEpoch,
position: _position.inMilliseconds,
);
await _audioPlayer.pause();
}
}
MediaControl playControl = const MediaControl(
androidIcon: 'drawable/ic_action_play_arrow',
label: 'Play',
action: MediaAction.play,
);
MediaControl pauseControl = const MediaControl(
androidIcon: 'drawable/ic_action_pause',
label: 'Pause',
action: MediaAction.pause,
);
MediaControl stopControl = const MediaControl(
androidIcon: 'drawable/ic_action_stop',
label: 'Stop',
action: MediaAction.stop,
);

View file

@ -19,6 +19,11 @@ class AudioRepositoryImpl implements AudioRepository {
Stream<PlaybackState> get playbackStateStream =>
_audioManager.playbackStateStream;
@override
Stream<int> get currentPositionStream {
return _audioManager.currentPositionStream;
}
@override
Future<Either<Failure, void>> playSong(int index, List<Song> songList) async {
final List<SongModel> songModelList =