Merge pull request #118 from FriederHannenheim/loop_mode_stop
Implement stop looping mode
This commit is contained in:
commit
8b58bf0b43
9 changed files with 99 additions and 31 deletions
18
src/assets/icons/loopmode_stop_black_24dp.svg
Normal file
18
src/assets/icons/loopmode_stop_black_24dp.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
enable-background="new 0 0 24 24"
|
||||
height="24px"
|
||||
viewBox="0 0 24 24"
|
||||
width="24px"
|
||||
fill="#000000"
|
||||
version="1.1"
|
||||
id="svg10"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs14" />
|
||||
<path
|
||||
id="path293"
|
||||
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:3.7032;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="M 6,5 C 5.4458745,5 5,5.4458746 5,6 v 12 c 0,0.554125 0.4458745,1 1,1 h 12 c 0.554124,0 1,-0.445875 1,-1 V 6 C 19,5.4458746 18.554124,5 18,5 Z m 6.689453,4 C 13.139453,9 13.5,9.3605481 13.5,9.8105469 V 14.25 C 13.5,14.66 13.16,15 12.75,15 12.34,15 12,14.66 12,14.25 V 11 H 11.119141 C 10.779141,11 10.5,10.720859 10.5,10.380859 c 0,-0.229997 0.129844,-0.4507812 0.339844,-0.5507809 L 12.330078,9.0898438 C 12.440078,9.0298438 12.559453,9 12.689453,9 Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 927 B |
Binary file not shown.
|
@ -1,5 +1,6 @@
|
|||
enum LoopMode {
|
||||
off,
|
||||
one,
|
||||
all
|
||||
all,
|
||||
stop
|
||||
}
|
|
@ -1,6 +1,18 @@
|
|||
/// Flutter icons MuckeIcons
|
||||
/// Copyright (C) 2023 by original authors @ fluttericon.com, fontello.com
|
||||
/// This font was generated by FlutterIcon.com, which is derived from Fontello.
|
||||
///
|
||||
/// To use this font, place it in your fonts/ directory and include the
|
||||
/// following in your pubspec.yaml
|
||||
///
|
||||
/// flutter:
|
||||
/// fonts:
|
||||
/// - family: MuckeIcons
|
||||
/// fonts:
|
||||
/// - asset: fonts/MuckeIcons.ttf
|
||||
///
|
||||
///
|
||||
///
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MuckeIcons {
|
||||
|
@ -9,22 +21,15 @@ class MuckeIcons {
|
|||
static const _kFontFam = 'MuckeIcons';
|
||||
static const String? _kFontPkg = null;
|
||||
|
||||
static const IconData exclude_always =
|
||||
IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData exclude_never =
|
||||
IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData exclude_shuffle =
|
||||
IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData exclude_shuffle_all =
|
||||
IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData favorite_1_3 =
|
||||
IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData favorite_2_3 =
|
||||
IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData shuffle_heart =
|
||||
IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData shuffle_none =
|
||||
IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData exclude_always = IconData(0xe800, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData exclude_never = IconData(0xe801, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData exclude_shuffle = IconData(0xe802, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData exclude_shuffle_all = IconData(0xe803, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData favorite_1_3 = IconData(0xe804, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData favorite_2_3 = IconData(0xe805, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData loopmode_stop = IconData(0xe806, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData shuffle_heart = IconData(0xe809, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData shuffle_none = IconData(0xe80a, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData link_both = IconData(0xe80b, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData link_next = IconData(0xe80c, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
static const IconData link_prev = IconData(0xe80d, fontFamily: _kFontFam, fontPackage: _kFontPkg);
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
|||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../../domain/entities/loop_mode.dart';
|
||||
import '../mucke_icons.dart';
|
||||
import '../state/audio_store.dart';
|
||||
|
||||
class LoopButton extends StatelessWidget {
|
||||
|
@ -32,18 +33,6 @@ class LoopButton extends StatelessWidget {
|
|||
},
|
||||
splashRadius: iconSize / 2 + 6.0,
|
||||
);
|
||||
case LoopMode.one:
|
||||
return IconButton(
|
||||
icon: const Icon(
|
||||
Icons.repeat_one_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
iconSize: iconSize,
|
||||
onPressed: () {
|
||||
audioStore.setLoopMode(LoopMode.off);
|
||||
},
|
||||
splashRadius: iconSize / 2 + 6.0,
|
||||
);
|
||||
case LoopMode.all:
|
||||
return IconButton(
|
||||
icon: const Icon(
|
||||
|
@ -56,6 +45,30 @@ class LoopButton extends StatelessWidget {
|
|||
},
|
||||
splashRadius: iconSize / 2 + 6.0,
|
||||
);
|
||||
case LoopMode.one:
|
||||
return IconButton(
|
||||
icon: const Icon(
|
||||
Icons.repeat_one_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
iconSize: iconSize,
|
||||
onPressed: () {
|
||||
audioStore.setLoopMode(LoopMode.stop);
|
||||
},
|
||||
splashRadius: iconSize / 2 + 6.0,
|
||||
);
|
||||
case LoopMode.stop:
|
||||
return IconButton(
|
||||
icon: const Icon(
|
||||
MuckeIcons.loopmode_stop,
|
||||
color: Colors.white,
|
||||
),
|
||||
iconSize: iconSize,
|
||||
onPressed: () {
|
||||
audioStore.setLoopMode(LoopMode.off);
|
||||
},
|
||||
splashRadius: iconSize / 2 + 6.0,
|
||||
);
|
||||
case null:
|
||||
return Container();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ abstract class AudioPlayerDataSource {
|
|||
Stream<PlaybackEventModel> get playbackEventStream;
|
||||
ValueStream<bool> get playingStream;
|
||||
ValueStream<Duration> get positionStream;
|
||||
ValueStream<Duration?> get durationStream;
|
||||
|
||||
Future<void> play();
|
||||
Future<void> pause();
|
||||
|
|
|
@ -26,7 +26,13 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
|
|||
|
||||
_audioPlayer.playingStream.listen((event) => _playingSubject.add(event));
|
||||
|
||||
_audioPlayer.positionStream.listen((event) => _positionSubject.add(event));
|
||||
_audioPlayer
|
||||
.createPositionStream(
|
||||
steps: 800,
|
||||
minPeriod: const Duration(milliseconds: 16),
|
||||
maxPeriod: const Duration(milliseconds: 100),
|
||||
)
|
||||
.listen((event) => _positionSubject.add(event));
|
||||
|
||||
_audioPlayer.playbackEventStream.listen((event) {
|
||||
if (event.processingState == ja.ProcessingState.completed && _audioPlayer.playing) {
|
||||
|
@ -35,6 +41,8 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
|
|||
}
|
||||
});
|
||||
|
||||
_audioPlayer.durationStream.listen((event) => _durationSubject.add(event));
|
||||
|
||||
_playbackEventModelStream = Rx.combineLatest2<ja.PlaybackEvent, bool, PlaybackEventModel>(
|
||||
_audioPlayer.playbackEventStream,
|
||||
_audioPlayer.playingStream,
|
||||
|
@ -60,6 +68,7 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
|
|||
final BehaviorSubject<PlaybackEventModel> _playbackEventSubject = BehaviorSubject();
|
||||
final BehaviorSubject<bool> _playingSubject = BehaviorSubject();
|
||||
final BehaviorSubject<Duration> _positionSubject = BehaviorSubject();
|
||||
final BehaviorSubject<Duration?> _durationSubject = BehaviorSubject();
|
||||
|
||||
late Stream<PlaybackEventModel> _playbackEventModelStream;
|
||||
|
||||
|
@ -75,6 +84,9 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
|
|||
@override
|
||||
ValueStream<bool> get playingStream => _playingSubject.stream;
|
||||
|
||||
@override
|
||||
ValueStream<Duration?> get durationStream => _durationSubject.stream;
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
await _currentIndexSubject.close();
|
||||
|
@ -491,7 +503,7 @@ class AudioPlayerDataSourceImpl implements AudioPlayerDataSource {
|
|||
await _audioPlayer.seek(duration * position);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int calcNewCurrentIndexOnMove(int currentIndex, int oldIndex, int newIndex) {
|
||||
int newCurrentIndex = currentIndex;
|
||||
|
|
|
@ -35,6 +35,8 @@ extension LoopModeToInt on LoopMode {
|
|||
return 1;
|
||||
case LoopMode.all:
|
||||
return 2;
|
||||
case LoopMode.stop:
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
@ -48,6 +50,8 @@ extension IntToLoopMode on int {
|
|||
return LoopMode.one;
|
||||
case 2:
|
||||
return LoopMode.all;
|
||||
case 3:
|
||||
return LoopMode.stop;
|
||||
default:
|
||||
return LoopMode.off;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,20 @@ class AudioPlayerRepositoryImpl implements AudioPlayerRepository {
|
|||
_updateCurrentSong(queue, currentIndexStream.value);
|
||||
}
|
||||
});
|
||||
positionStream.listen((position) async {
|
||||
final durationMs = _audioPlayerDataSource.durationStream.valueOrNull?.inMilliseconds;
|
||||
final positionMs = position.inMilliseconds;
|
||||
|
||||
if (loopModeStream.value == LoopMode.stop &&
|
||||
durationMs != null &&
|
||||
// less than 101 milliseconds in the song remaining
|
||||
positionMs > durationMs - 101 &&
|
||||
// don't skip to next if we aren't playing
|
||||
_audioPlayerDataSource.playingStream.value) {
|
||||
await pause();
|
||||
await seekToNext();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static final _log = FimberLog('AudioPlayerRepositoryImpl');
|
||||
|
|
Loading…
Add table
Reference in a new issue