implemented current position
This commit is contained in:
parent
3b7588831c
commit
494f2aa28b
11 changed files with 210 additions and 102 deletions
|
@ -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();
|
||||
|
|
|
@ -90,7 +90,7 @@ class CurrentlyPlayingPage extends StatelessWidget {
|
|||
const Spacer(
|
||||
flex: 3,
|
||||
),
|
||||
TimeProgressIndicator(),
|
||||
const TimeProgressIndicator(),
|
||||
const Spacer(
|
||||
flex: 3,
|
||||
),
|
||||
|
|
|
@ -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);
|
||||
}));
|
||||
|
|
|
@ -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}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
|
|
87
lib/system/datasources/audio_player_task.dart
Normal file
87
lib/system/datasources/audio_player_task.dart
Normal 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,
|
||||
);
|
|
@ -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 =
|
||||
|
|
Loading…
Add table
Reference in a new issue