first step to loop mode implementation
This commit is contained in:
parent
9e4459bcc3
commit
f6e025c017
19 changed files with 352 additions and 21 deletions
5
lib/domain/entities/loop_mode.dart
Normal file
5
lib/domain/entities/loop_mode.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
enum LoopMode {
|
||||||
|
off,
|
||||||
|
one,
|
||||||
|
all
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
|
|
||||||
import '../../core/error/failures.dart';
|
import '../../core/error/failures.dart';
|
||||||
|
import '../entities/loop_mode.dart';
|
||||||
import '../entities/playback_state.dart';
|
import '../entities/playback_state.dart';
|
||||||
import '../entities/shuffle_mode.dart';
|
import '../entities/shuffle_mode.dart';
|
||||||
import '../entities/song.dart';
|
import '../entities/song.dart';
|
||||||
|
@ -19,6 +20,9 @@ abstract class AudioRepository {
|
||||||
Future<Either<Failure, void>> skipToNext();
|
Future<Either<Failure, void>> skipToNext();
|
||||||
Future<Either<Failure, void>> skipToPrevious();
|
Future<Either<Failure, void>> skipToPrevious();
|
||||||
Future<Either<Failure, void>> setShuffleMode(ShuffleMode shuffleMode);
|
Future<Either<Failure, void>> setShuffleMode(ShuffleMode shuffleMode);
|
||||||
|
|
||||||
|
Future<void> setLoopMode(LoopMode loopMode);
|
||||||
|
|
||||||
Future<Either<Failure, void>> shuffleAll();
|
Future<Either<Failure, void>> shuffleAll();
|
||||||
Future<Either<Failure, void>> addToQueue(Song song);
|
Future<Either<Failure, void>> addToQueue(Song song);
|
||||||
Future<Either<Failure, void>> moveQueueItem(int oldIndex, int newIndex);
|
Future<Either<Failure, void>> moveQueueItem(int oldIndex, int newIndex);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'package:dartz/dartz.dart';
|
||||||
import '../../core/error/failures.dart';
|
import '../../core/error/failures.dart';
|
||||||
import '../entities/album.dart';
|
import '../entities/album.dart';
|
||||||
import '../entities/artist.dart';
|
import '../entities/artist.dart';
|
||||||
|
import '../entities/loop_mode.dart';
|
||||||
import '../entities/song.dart';
|
import '../entities/song.dart';
|
||||||
|
|
||||||
abstract class MusicDataRepository {
|
abstract class MusicDataRepository {
|
||||||
|
@ -10,6 +11,8 @@ abstract class MusicDataRepository {
|
||||||
Stream<List<Song>> getAlbumSongStream(Album album);
|
Stream<List<Song>> getAlbumSongStream(Album album);
|
||||||
Stream<List<Song>> get queueStream;
|
Stream<List<Song>> get queueStream;
|
||||||
Stream<int> get currentIndexStream;
|
Stream<int> get currentIndexStream;
|
||||||
|
|
||||||
|
Stream<LoopMode> get loopModeStream;
|
||||||
|
|
||||||
Future<Either<Failure, List<Song>>> getSongs();
|
Future<Either<Failure, List<Song>>> getSongs();
|
||||||
Future<Either<Failure, List<Song>>> getSongsFromAlbum(Album album);
|
Future<Either<Failure, List<Song>>> getSongsFromAlbum(Album album);
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:mucke/domain/repositories/music_data_repository.dart';
|
|
||||||
|
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
import '../../domain/entities/playback_state.dart';
|
import '../../domain/entities/playback_state.dart';
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
import '../../domain/entities/song.dart';
|
import '../../domain/entities/song.dart';
|
||||||
import '../../domain/repositories/audio_repository.dart';
|
import '../../domain/repositories/audio_repository.dart';
|
||||||
|
import '../../domain/repositories/music_data_repository.dart';
|
||||||
|
|
||||||
part 'audio_store.g.dart';
|
part 'audio_store.g.dart';
|
||||||
|
|
||||||
|
@ -30,6 +31,8 @@ abstract class _AudioStore with Store {
|
||||||
shuffleModeStream =
|
shuffleModeStream =
|
||||||
_audioRepository.shuffleModeStream.asObservable(initialValue: ShuffleMode.none);
|
_audioRepository.shuffleModeStream.asObservable(initialValue: ShuffleMode.none);
|
||||||
|
|
||||||
|
loopModeStream = _musicDataRepository.loopModeStream.asObservable();
|
||||||
|
|
||||||
playbackStateStream = _audioRepository.playbackStateStream.asObservable();
|
playbackStateStream = _audioRepository.playbackStateStream.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +73,9 @@ abstract class _AudioStore with Store {
|
||||||
@observable
|
@observable
|
||||||
ObservableStream<ShuffleMode> shuffleModeStream;
|
ObservableStream<ShuffleMode> shuffleModeStream;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
ObservableStream<LoopMode> loopModeStream;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
Future<void> playSong(int index, List<Song> songList) async {
|
Future<void> playSong(int index, List<Song> songList) async {
|
||||||
_audioRepository.playSong(index, songList);
|
_audioRepository.playSong(index, songList);
|
||||||
|
@ -99,6 +105,11 @@ abstract class _AudioStore with Store {
|
||||||
_audioRepository.setShuffleMode(shuffleMode);
|
_audioRepository.setShuffleMode(shuffleMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||||
|
print('setLoopMode');
|
||||||
|
_audioRepository.setLoopMode(loopMode);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> shuffleAll() async {
|
Future<void> shuffleAll() async {
|
||||||
_audioRepository.shuffleAll();
|
_audioRepository.shuffleAll();
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,6 +110,21 @@ mixin _$AudioStore on _AudioStore, Store {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _$loopModeStreamAtom = Atom(name: '_AudioStore.loopModeStream');
|
||||||
|
|
||||||
|
@override
|
||||||
|
ObservableStream<LoopMode> get loopModeStream {
|
||||||
|
_$loopModeStreamAtom.reportRead();
|
||||||
|
return super.loopModeStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
set loopModeStream(ObservableStream<LoopMode> value) {
|
||||||
|
_$loopModeStreamAtom.reportWrite(value, super.loopModeStream, () {
|
||||||
|
super.loopModeStream = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
final _$playSongAsyncAction = AsyncAction('_AudioStore.playSong');
|
final _$playSongAsyncAction = AsyncAction('_AudioStore.playSong');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -154,6 +169,7 @@ currentPositionStream: ${currentPositionStream},
|
||||||
queueStream: ${queueStream},
|
queueStream: ${queueStream},
|
||||||
queueIndexStream: ${queueIndexStream},
|
queueIndexStream: ${queueIndexStream},
|
||||||
shuffleModeStream: ${shuffleModeStream},
|
shuffleModeStream: ${shuffleModeStream},
|
||||||
|
loopModeStream: ${loopModeStream},
|
||||||
currentSong: ${currentSong}
|
currentSong: ${currentSong}
|
||||||
''';
|
''';
|
||||||
}
|
}
|
||||||
|
|
60
lib/presentation/widgets/loop_button.dart
Normal file
60
lib/presentation/widgets/loop_button.dart
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
|
import '../state/audio_store.dart';
|
||||||
|
|
||||||
|
class LoopButton extends StatelessWidget {
|
||||||
|
const LoopButton({Key key, this.iconSize = 20.0}) : super(key: key);
|
||||||
|
|
||||||
|
final double iconSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final AudioStore audioStore = Provider.of<AudioStore>(context);
|
||||||
|
|
||||||
|
return Observer(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
if (audioStore.loopModeStream != null) {
|
||||||
|
switch (audioStore.loopModeStream.value) {
|
||||||
|
case LoopMode.off:
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.repeat_rounded,
|
||||||
|
color: Colors.white30,
|
||||||
|
),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () {
|
||||||
|
audioStore.setLoopMode(LoopMode.all);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case LoopMode.one:
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.repeat_one_rounded,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () {
|
||||||
|
audioStore.setLoopMode(LoopMode.off);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
case LoopMode.all:
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.repeat_rounded,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () {
|
||||||
|
audioStore.setLoopMode(LoopMode.one);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'loop_button.dart';
|
||||||
import 'next_button.dart';
|
import 'next_button.dart';
|
||||||
import 'play_pause_button.dart';
|
import 'play_pause_button.dart';
|
||||||
import 'previous_button.dart';
|
import 'previous_button.dart';
|
||||||
|
@ -12,14 +13,7 @@ class PlaybackControl extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
children: const [
|
children: const [
|
||||||
IconButton(
|
LoopButton(iconSize: 20.0),
|
||||||
icon: Icon(
|
|
||||||
Icons.repeat,
|
|
||||||
size: 20.0,
|
|
||||||
color: Colors.white10,
|
|
||||||
),
|
|
||||||
onPressed: null,
|
|
||||||
),
|
|
||||||
PreviousButton(iconSize: 32.0),
|
PreviousButton(iconSize: 32.0),
|
||||||
PlayPauseButton(
|
PlayPauseButton(
|
||||||
circle: true,
|
circle: true,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:math';
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
import '../../domain/entities/player_state.dart';
|
import '../../domain/entities/player_state.dart';
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
import '../datasources/music_data_source_contract.dart';
|
import '../datasources/music_data_source_contract.dart';
|
||||||
|
@ -31,14 +32,20 @@ class MyAudioHandler extends BaseAudioHandler {
|
||||||
customEventSubject.add({SHUFFLE_MODE: shuffleMode});
|
customEventSubject.add({SHUFFLE_MODE: shuffleMode});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_audioPlayer.loopModeStream.listen((event) {
|
||||||
|
_musicDataSource.setLoopMode(event);
|
||||||
|
});
|
||||||
|
|
||||||
_initAudioPlayer();
|
_initAudioPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initAudioPlayer() async {
|
Future<void> _initAudioPlayer() async {
|
||||||
_audioPlayer.loadQueue(
|
if (_musicDataSource.queueStream != null && _musicDataSource.currentIndexStream != null) {
|
||||||
queue: await _musicDataSource.queueStream.first,
|
_audioPlayer.loadQueue(
|
||||||
startIndex: await _musicDataSource.currentIndexStream.first,
|
queue: await _musicDataSource.queueStream.first,
|
||||||
);
|
startIndex: await _musicDataSource.currentIndexStream.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final AudioPlayer _audioPlayer;
|
final AudioPlayer _audioPlayer;
|
||||||
|
@ -89,6 +96,8 @@ class MyAudioHandler extends BaseAudioHandler {
|
||||||
return onAppLifecycleResumed();
|
return onAppLifecycleResumed();
|
||||||
case SET_SHUFFLE_MODE:
|
case SET_SHUFFLE_MODE:
|
||||||
return setCustomShuffleMode(arguments['SHUFFLE_MODE'] as ShuffleMode);
|
return setCustomShuffleMode(arguments['SHUFFLE_MODE'] as ShuffleMode);
|
||||||
|
case SET_LOOP_MODE:
|
||||||
|
return setCustomLoopMode(arguments['LOOP_MODE'] as LoopMode);
|
||||||
case SHUFFLE_ALL:
|
case SHUFFLE_ALL:
|
||||||
return shuffleAll();
|
return shuffleAll();
|
||||||
case MOVE_QUEUE_ITEM:
|
case MOVE_QUEUE_ITEM:
|
||||||
|
@ -118,6 +127,11 @@ class MyAudioHandler extends BaseAudioHandler {
|
||||||
_audioPlayer.setShuffleMode(mode, true);
|
_audioPlayer.setShuffleMode(mode, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> setCustomLoopMode(LoopMode mode) async {
|
||||||
|
|
||||||
|
_audioPlayer.setLoopMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> shuffleAll() async {
|
Future<void> shuffleAll() async {
|
||||||
_audioPlayer.setShuffleMode(ShuffleMode.plus, false);
|
_audioPlayer.setShuffleMode(ShuffleMode.plus, false);
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:audio_service/audio_service.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
import '../../domain/entities/playback_state.dart' as entity;
|
import '../../domain/entities/playback_state.dart' as entity;
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
import '../models/playback_state_model.dart';
|
import '../models/playback_state_model.dart';
|
||||||
|
@ -115,6 +116,12 @@ class AudioManagerImpl implements AudioManager {
|
||||||
await _audioHandler.customAction(SET_SHUFFLE_MODE, {'SHUFFLE_MODE': shuffleMode});
|
await _audioHandler.customAction(SET_SHUFFLE_MODE, {'SHUFFLE_MODE': shuffleMode});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||||
|
print('setLoopMode!!');
|
||||||
|
await _audioHandler.customAction(SET_LOOP_MODE, {'LOOP_MODE': loopMode});
|
||||||
|
}
|
||||||
|
|
||||||
Stream<T> _filterStream<S, T>(Stream<S> stream, Conversion<S, T> fn) async* {
|
Stream<T> _filterStream<S, T>(Stream<S> stream, Conversion<S, T> fn) async* {
|
||||||
T lastItem;
|
T lastItem;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
import '../../domain/entities/playback_state.dart';
|
import '../../domain/entities/playback_state.dart';
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
import '../models/song_model.dart';
|
import '../models/song_model.dart';
|
||||||
|
@ -17,6 +18,7 @@ abstract class AudioManager {
|
||||||
Future<void> skipToNext();
|
Future<void> skipToNext();
|
||||||
Future<void> skipToPrevious();
|
Future<void> skipToPrevious();
|
||||||
Future<void> setShuffleMode(ShuffleMode shuffleMode);
|
Future<void> setShuffleMode(ShuffleMode shuffleMode);
|
||||||
|
Future<void> setLoopMode(LoopMode loopMode);
|
||||||
Future<void> shuffleAll();
|
Future<void> shuffleAll();
|
||||||
Future<void> addToQueue(SongModel songModel);
|
Future<void> addToQueue(SongModel songModel);
|
||||||
Future<void> moveQueueItem(int oldIndex, int newIndex);
|
Future<void> moveQueueItem(int oldIndex, int newIndex);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
import '../models/player_state_model.dart';
|
import '../models/player_state_model.dart';
|
||||||
import '../models/queue_item_model.dart';
|
import '../models/queue_item_model.dart';
|
||||||
|
@ -12,6 +13,7 @@ abstract class AudioPlayer {
|
||||||
ValueStream<Duration> get positionStream;
|
ValueStream<Duration> get positionStream;
|
||||||
ValueStream<List<QueueItemModel>> get queueStream;
|
ValueStream<List<QueueItemModel>> get queueStream;
|
||||||
ValueStream<ShuffleMode> get shuffleModeStream;
|
ValueStream<ShuffleMode> get shuffleModeStream;
|
||||||
|
ValueStream<LoopMode> get loopModeStream;
|
||||||
|
|
||||||
Future<void> play();
|
Future<void> play();
|
||||||
Future<void> pause();
|
Future<void> pause();
|
||||||
|
@ -28,6 +30,7 @@ abstract class AudioPlayer {
|
||||||
|
|
||||||
|
|
||||||
Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue);
|
Future<void> setShuffleMode(ShuffleMode shuffleMode, bool updateQueue);
|
||||||
|
Future<void> setLoopMode(LoopMode loopMode);
|
||||||
|
|
||||||
Future<void> playSongList(List<SongModel> songs, int startIndex);
|
Future<void> playSongList(List<SongModel> songs, int startIndex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:just_audio/just_audio.dart' as ja;
|
import 'package:just_audio/just_audio.dart' as ja;
|
||||||
import 'package:rxdart/rxdart.dart';
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
import '../../domain/entities/queue_item.dart';
|
import '../../domain/entities/queue_item.dart';
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
|
import '../models/loop_mode_model.dart';
|
||||||
import '../models/player_state_model.dart';
|
import '../models/player_state_model.dart';
|
||||||
import '../models/queue_item_model.dart';
|
import '../models/queue_item_model.dart';
|
||||||
import '../models/song_model.dart';
|
import '../models/song_model.dart';
|
||||||
|
@ -24,6 +26,10 @@ class AudioPlayerImpl implements AudioPlayer {
|
||||||
_playerStateSubject.add(PlayerStateModel.fromJAPlayerState(event));
|
_playerStateSubject.add(PlayerStateModel.fromJAPlayerState(event));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_audioPlayer.loopModeStream.listen((event) {
|
||||||
|
_loopModeSubject.add(event.toEntity());
|
||||||
|
});
|
||||||
|
|
||||||
_queueSubject.listen((event) {
|
_queueSubject.listen((event) {
|
||||||
_currentSongSubject.add(event[_currentIndexSubject.value].song);
|
_currentSongSubject.add(event[_currentIndexSubject.value].song);
|
||||||
});
|
});
|
||||||
|
@ -42,6 +48,7 @@ class AudioPlayerImpl implements AudioPlayer {
|
||||||
final BehaviorSubject<Duration> _positionSubject = BehaviorSubject();
|
final BehaviorSubject<Duration> _positionSubject = BehaviorSubject();
|
||||||
final BehaviorSubject<List<QueueItemModel>> _queueSubject = BehaviorSubject();
|
final BehaviorSubject<List<QueueItemModel>> _queueSubject = BehaviorSubject();
|
||||||
final BehaviorSubject<ShuffleMode> _shuffleModeSubject = BehaviorSubject.seeded(ShuffleMode.none);
|
final BehaviorSubject<ShuffleMode> _shuffleModeSubject = BehaviorSubject.seeded(ShuffleMode.none);
|
||||||
|
final BehaviorSubject<LoopMode> _loopModeSubject = BehaviorSubject();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ValueStream<int> get currentIndexStream => _currentIndexSubject.stream;
|
ValueStream<int> get currentIndexStream => _currentIndexSubject.stream;
|
||||||
|
@ -61,8 +68,18 @@ class AudioPlayerImpl implements AudioPlayer {
|
||||||
@override
|
@override
|
||||||
ValueStream<ShuffleMode> get shuffleModeStream => _shuffleModeSubject.stream;
|
ValueStream<ShuffleMode> get shuffleModeStream => _shuffleModeSubject.stream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
ValueStream<LoopMode> get loopModeStream => _loopModeSubject.stream;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
|
await _currentIndexSubject.close();
|
||||||
|
await _currentSongSubject.close();
|
||||||
|
await _playerStateSubject.close();
|
||||||
|
await _positionSubject.close();
|
||||||
|
await _queueSubject.close();
|
||||||
|
await _shuffleModeSubject.close();
|
||||||
|
await _loopModeSubject.close();
|
||||||
await _audioPlayer.dispose();
|
await _audioPlayer.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,6 +191,12 @@ class AudioPlayerImpl implements AudioPlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||||
|
print('ap loopmode');
|
||||||
|
await _audioPlayer.setLoopMode(loopMode.toJA());
|
||||||
|
}
|
||||||
|
|
||||||
void _updateQueue(ja.ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) {
|
void _updateQueue(ja.ConcatenatingAudioSource newQueue, QueueItem currentQueueItem) {
|
||||||
final int index = currentQueueItem.originalIndex;
|
final int index = currentQueueItem.originalIndex;
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,6 @@ const String PLAY_WITH_CONTEXT = 'PLAY_WITH_CONTEXT';
|
||||||
const String APP_LIFECYCLE_RESUMED = 'APP_LIFECYCLE_RESUMED';
|
const String APP_LIFECYCLE_RESUMED = 'APP_LIFECYCLE_RESUMED';
|
||||||
const String SHUFFLE_ALL = 'SHUFFLE_ALL';
|
const String SHUFFLE_ALL = 'SHUFFLE_ALL';
|
||||||
const String SET_SHUFFLE_MODE = 'SET_SHUFFLE_MODE';
|
const String SET_SHUFFLE_MODE = 'SET_SHUFFLE_MODE';
|
||||||
|
const String SET_LOOP_MODE = 'SET_LOOP_MODE';
|
||||||
const String MOVE_QUEUE_ITEM = 'MOVE_QUEUE_ITEM';
|
const String MOVE_QUEUE_ITEM = 'MOVE_QUEUE_ITEM';
|
||||||
const String REMOVE_QUEUE_ITEM = 'REMOVE_QUEUE_ITEM';
|
const String REMOVE_QUEUE_ITEM = 'REMOVE_QUEUE_ITEM';
|
|
@ -7,8 +7,11 @@ import 'package:moor/moor.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
import '../models/album_model.dart';
|
import '../models/album_model.dart';
|
||||||
import '../models/artist_model.dart';
|
import '../models/artist_model.dart';
|
||||||
|
import '../models/loop_mode_model.dart';
|
||||||
import '../models/queue_item_model.dart';
|
import '../models/queue_item_model.dart';
|
||||||
import '../models/song_model.dart';
|
import '../models/song_model.dart';
|
||||||
import 'music_data_source_contract.dart';
|
import 'music_data_source_contract.dart';
|
||||||
|
@ -69,6 +72,8 @@ class QueueEntries extends Table {
|
||||||
@DataClassName('PersistentPlayerState')
|
@DataClassName('PersistentPlayerState')
|
||||||
class PlayerState extends Table {
|
class PlayerState extends Table {
|
||||||
IntColumn get index => integer()();
|
IntColumn get index => integer()();
|
||||||
|
IntColumn get shuffleMode => integer().withDefault(const Constant(0))();
|
||||||
|
IntColumn get loopMode => integer().withDefault(const Constant(0))();
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseMoor(tables: [Artists, Albums, Songs, QueueEntries, PlayerState])
|
@UseMoor(tables: [Artists, Albums, Songs, QueueEntries, PlayerState])
|
||||||
|
@ -293,6 +298,32 @@ class MoorMusicDataSource extends _$MoorMusicDataSource implements MusicDataSour
|
||||||
into(playerState).insert(PlayerStateCompanion(index: Value(index)));
|
into(playerState).insert(PlayerStateCompanion(index: Value(index)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<LoopMode> get loopModeStream {
|
||||||
|
return select(playerState).watchSingle().map((event) => event.loopMode.toLoopMode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||||
|
print('setLoopMode!!!');
|
||||||
|
final currentState = await select(playerState).getSingle();
|
||||||
|
if (currentState != null) {
|
||||||
|
update(playerState).write(PlayerStateCompanion(loopMode: Value(loopMode.toInt())));
|
||||||
|
} else {
|
||||||
|
into(playerState).insert(PlayerStateCompanion(loopMode: Value(loopMode.toInt())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setShuffleMode(ShuffleMode shuffleMode) {
|
||||||
|
// TODO: implement setShuffleMode
|
||||||
|
throw UnimplementedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// TODO: implement shuffleModeStream
|
||||||
|
Stream<ShuffleMode> get shuffleModeStream => throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyDatabase _openConnection() {
|
LazyDatabase _openConnection() {
|
||||||
|
|
|
@ -1508,7 +1508,12 @@ class $QueueEntriesTable extends QueueEntries
|
||||||
class PersistentPlayerState extends DataClass
|
class PersistentPlayerState extends DataClass
|
||||||
implements Insertable<PersistentPlayerState> {
|
implements Insertable<PersistentPlayerState> {
|
||||||
final int index;
|
final int index;
|
||||||
PersistentPlayerState({@required this.index});
|
final int shuffleMode;
|
||||||
|
final int loopMode;
|
||||||
|
PersistentPlayerState(
|
||||||
|
{@required this.index,
|
||||||
|
@required this.shuffleMode,
|
||||||
|
@required this.loopMode});
|
||||||
factory PersistentPlayerState.fromData(
|
factory PersistentPlayerState.fromData(
|
||||||
Map<String, dynamic> data, GeneratedDatabase db,
|
Map<String, dynamic> data, GeneratedDatabase db,
|
||||||
{String prefix}) {
|
{String prefix}) {
|
||||||
|
@ -1516,6 +1521,10 @@ class PersistentPlayerState extends DataClass
|
||||||
final intType = db.typeSystem.forDartType<int>();
|
final intType = db.typeSystem.forDartType<int>();
|
||||||
return PersistentPlayerState(
|
return PersistentPlayerState(
|
||||||
index: intType.mapFromDatabaseResponse(data['${effectivePrefix}index']),
|
index: intType.mapFromDatabaseResponse(data['${effectivePrefix}index']),
|
||||||
|
shuffleMode: intType
|
||||||
|
.mapFromDatabaseResponse(data['${effectivePrefix}shuffle_mode']),
|
||||||
|
loopMode:
|
||||||
|
intType.mapFromDatabaseResponse(data['${effectivePrefix}loop_mode']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
|
@ -1524,6 +1533,12 @@ class PersistentPlayerState extends DataClass
|
||||||
if (!nullToAbsent || index != null) {
|
if (!nullToAbsent || index != null) {
|
||||||
map['index'] = Variable<int>(index);
|
map['index'] = Variable<int>(index);
|
||||||
}
|
}
|
||||||
|
if (!nullToAbsent || shuffleMode != null) {
|
||||||
|
map['shuffle_mode'] = Variable<int>(shuffleMode);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || loopMode != null) {
|
||||||
|
map['loop_mode'] = Variable<int>(loopMode);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1531,6 +1546,12 @@ class PersistentPlayerState extends DataClass
|
||||||
return PlayerStateCompanion(
|
return PlayerStateCompanion(
|
||||||
index:
|
index:
|
||||||
index == null && nullToAbsent ? const Value.absent() : Value(index),
|
index == null && nullToAbsent ? const Value.absent() : Value(index),
|
||||||
|
shuffleMode: shuffleMode == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(shuffleMode),
|
||||||
|
loopMode: loopMode == null && nullToAbsent
|
||||||
|
? const Value.absent()
|
||||||
|
: Value(loopMode),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1539,6 +1560,8 @@ class PersistentPlayerState extends DataClass
|
||||||
serializer ??= moorRuntimeOptions.defaultSerializer;
|
serializer ??= moorRuntimeOptions.defaultSerializer;
|
||||||
return PersistentPlayerState(
|
return PersistentPlayerState(
|
||||||
index: serializer.fromJson<int>(json['index']),
|
index: serializer.fromJson<int>(json['index']),
|
||||||
|
shuffleMode: serializer.fromJson<int>(json['shuffleMode']),
|
||||||
|
loopMode: serializer.fromJson<int>(json['loopMode']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
|
@ -1546,47 +1569,71 @@ class PersistentPlayerState extends DataClass
|
||||||
serializer ??= moorRuntimeOptions.defaultSerializer;
|
serializer ??= moorRuntimeOptions.defaultSerializer;
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'index': serializer.toJson<int>(index),
|
'index': serializer.toJson<int>(index),
|
||||||
|
'shuffleMode': serializer.toJson<int>(shuffleMode),
|
||||||
|
'loopMode': serializer.toJson<int>(loopMode),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
PersistentPlayerState copyWith({int index}) => PersistentPlayerState(
|
PersistentPlayerState copyWith({int index, int shuffleMode, int loopMode}) =>
|
||||||
|
PersistentPlayerState(
|
||||||
index: index ?? this.index,
|
index: index ?? this.index,
|
||||||
|
shuffleMode: shuffleMode ?? this.shuffleMode,
|
||||||
|
loopMode: loopMode ?? this.loopMode,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('PersistentPlayerState(')
|
return (StringBuffer('PersistentPlayerState(')
|
||||||
..write('index: $index')
|
..write('index: $index, ')
|
||||||
|
..write('shuffleMode: $shuffleMode, ')
|
||||||
|
..write('loopMode: $loopMode')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => $mrjf(index.hashCode);
|
int get hashCode => $mrjf(
|
||||||
|
$mrjc(index.hashCode, $mrjc(shuffleMode.hashCode, loopMode.hashCode)));
|
||||||
@override
|
@override
|
||||||
bool operator ==(dynamic other) =>
|
bool operator ==(dynamic other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
(other is PersistentPlayerState && other.index == this.index);
|
(other is PersistentPlayerState &&
|
||||||
|
other.index == this.index &&
|
||||||
|
other.shuffleMode == this.shuffleMode &&
|
||||||
|
other.loopMode == this.loopMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlayerStateCompanion extends UpdateCompanion<PersistentPlayerState> {
|
class PlayerStateCompanion extends UpdateCompanion<PersistentPlayerState> {
|
||||||
final Value<int> index;
|
final Value<int> index;
|
||||||
|
final Value<int> shuffleMode;
|
||||||
|
final Value<int> loopMode;
|
||||||
const PlayerStateCompanion({
|
const PlayerStateCompanion({
|
||||||
this.index = const Value.absent(),
|
this.index = const Value.absent(),
|
||||||
|
this.shuffleMode = const Value.absent(),
|
||||||
|
this.loopMode = const Value.absent(),
|
||||||
});
|
});
|
||||||
PlayerStateCompanion.insert({
|
PlayerStateCompanion.insert({
|
||||||
@required int index,
|
@required int index,
|
||||||
|
this.shuffleMode = const Value.absent(),
|
||||||
|
this.loopMode = const Value.absent(),
|
||||||
}) : index = Value(index);
|
}) : index = Value(index);
|
||||||
static Insertable<PersistentPlayerState> custom({
|
static Insertable<PersistentPlayerState> custom({
|
||||||
Expression<int> index,
|
Expression<int> index,
|
||||||
|
Expression<int> shuffleMode,
|
||||||
|
Expression<int> loopMode,
|
||||||
}) {
|
}) {
|
||||||
return RawValuesInsertable({
|
return RawValuesInsertable({
|
||||||
if (index != null) 'index': index,
|
if (index != null) 'index': index,
|
||||||
|
if (shuffleMode != null) 'shuffle_mode': shuffleMode,
|
||||||
|
if (loopMode != null) 'loop_mode': loopMode,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerStateCompanion copyWith({Value<int> index}) {
|
PlayerStateCompanion copyWith(
|
||||||
|
{Value<int> index, Value<int> shuffleMode, Value<int> loopMode}) {
|
||||||
return PlayerStateCompanion(
|
return PlayerStateCompanion(
|
||||||
index: index ?? this.index,
|
index: index ?? this.index,
|
||||||
|
shuffleMode: shuffleMode ?? this.shuffleMode,
|
||||||
|
loopMode: loopMode ?? this.loopMode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1596,13 +1643,21 @@ class PlayerStateCompanion extends UpdateCompanion<PersistentPlayerState> {
|
||||||
if (index.present) {
|
if (index.present) {
|
||||||
map['index'] = Variable<int>(index.value);
|
map['index'] = Variable<int>(index.value);
|
||||||
}
|
}
|
||||||
|
if (shuffleMode.present) {
|
||||||
|
map['shuffle_mode'] = Variable<int>(shuffleMode.value);
|
||||||
|
}
|
||||||
|
if (loopMode.present) {
|
||||||
|
map['loop_mode'] = Variable<int>(loopMode.value);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return (StringBuffer('PlayerStateCompanion(')
|
return (StringBuffer('PlayerStateCompanion(')
|
||||||
..write('index: $index')
|
..write('index: $index, ')
|
||||||
|
..write('shuffleMode: $shuffleMode, ')
|
||||||
|
..write('loopMode: $loopMode')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
@ -1625,8 +1680,28 @@ class $PlayerStateTable extends PlayerState
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final VerificationMeta _shuffleModeMeta =
|
||||||
|
const VerificationMeta('shuffleMode');
|
||||||
|
GeneratedIntColumn _shuffleMode;
|
||||||
@override
|
@override
|
||||||
List<GeneratedColumn> get $columns => [index];
|
GeneratedIntColumn get shuffleMode =>
|
||||||
|
_shuffleMode ??= _constructShuffleMode();
|
||||||
|
GeneratedIntColumn _constructShuffleMode() {
|
||||||
|
return GeneratedIntColumn('shuffle_mode', $tableName, false,
|
||||||
|
defaultValue: const Constant(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
final VerificationMeta _loopModeMeta = const VerificationMeta('loopMode');
|
||||||
|
GeneratedIntColumn _loopMode;
|
||||||
|
@override
|
||||||
|
GeneratedIntColumn get loopMode => _loopMode ??= _constructLoopMode();
|
||||||
|
GeneratedIntColumn _constructLoopMode() {
|
||||||
|
return GeneratedIntColumn('loop_mode', $tableName, false,
|
||||||
|
defaultValue: const Constant(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<GeneratedColumn> get $columns => [index, shuffleMode, loopMode];
|
||||||
@override
|
@override
|
||||||
$PlayerStateTable get asDslTable => this;
|
$PlayerStateTable get asDslTable => this;
|
||||||
@override
|
@override
|
||||||
|
@ -1645,6 +1720,16 @@ class $PlayerStateTable extends PlayerState
|
||||||
} else if (isInserting) {
|
} else if (isInserting) {
|
||||||
context.missing(_indexMeta);
|
context.missing(_indexMeta);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('shuffle_mode')) {
|
||||||
|
context.handle(
|
||||||
|
_shuffleModeMeta,
|
||||||
|
shuffleMode.isAcceptableOrUnknown(
|
||||||
|
data['shuffle_mode'], _shuffleModeMeta));
|
||||||
|
}
|
||||||
|
if (data.containsKey('loop_mode')) {
|
||||||
|
context.handle(_loopModeMeta,
|
||||||
|
loopMode.isAcceptableOrUnknown(data['loop_mode'], _loopModeMeta));
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
import '../models/album_model.dart';
|
import '../models/album_model.dart';
|
||||||
import '../models/artist_model.dart';
|
import '../models/artist_model.dart';
|
||||||
import '../models/queue_item_model.dart';
|
import '../models/queue_item_model.dart';
|
||||||
|
@ -14,6 +16,10 @@ abstract class MusicDataSource {
|
||||||
Stream<List<QueueItemModel>> get queueStream;
|
Stream<List<QueueItemModel>> get queueStream;
|
||||||
Future<void> setCurrentIndex(int index);
|
Future<void> setCurrentIndex(int index);
|
||||||
Stream<int> get currentIndexStream;
|
Stream<int> get currentIndexStream;
|
||||||
|
Future<void> setShuffleMode(ShuffleMode shuffleMode);
|
||||||
|
Stream<ShuffleMode> get shuffleModeStream;
|
||||||
|
Future<void> setLoopMode(LoopMode loopMode);
|
||||||
|
Stream<LoopMode> get loopModeStream;
|
||||||
|
|
||||||
/// Insert album into the database. Return the ID of the inserted album.
|
/// Insert album into the database. Return the ID of the inserted album.
|
||||||
Future<int> insertAlbum(AlbumModel albumModel);
|
Future<int> insertAlbum(AlbumModel albumModel);
|
||||||
|
|
55
lib/system/models/loop_mode_model.dart
Normal file
55
lib/system/models/loop_mode_model.dart
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import 'package:just_audio/just_audio.dart' as ja;
|
||||||
|
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
|
|
||||||
|
extension LoopModeToJA on LoopMode {
|
||||||
|
ja.LoopMode toJA() {
|
||||||
|
switch(this) {
|
||||||
|
case LoopMode.one:
|
||||||
|
return ja.LoopMode.one;
|
||||||
|
case LoopMode.all:
|
||||||
|
return ja.LoopMode.all;
|
||||||
|
default:
|
||||||
|
return ja.LoopMode.off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension JALoopModeToEntity on ja.LoopMode {
|
||||||
|
LoopMode toEntity() {
|
||||||
|
switch(this) {
|
||||||
|
case ja.LoopMode.one:
|
||||||
|
return LoopMode.one;
|
||||||
|
case ja.LoopMode.all:
|
||||||
|
return LoopMode.all;
|
||||||
|
default:
|
||||||
|
return LoopMode.off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LoopModeToInt on LoopMode {
|
||||||
|
int toInt() {
|
||||||
|
switch(this) {
|
||||||
|
case LoopMode.one:
|
||||||
|
return 1;
|
||||||
|
case LoopMode.all:
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IntToLoopMode on int {
|
||||||
|
LoopMode toLoopMode() {
|
||||||
|
switch(this) {
|
||||||
|
case 1:
|
||||||
|
return LoopMode.one;
|
||||||
|
case 2:
|
||||||
|
return LoopMode.all;
|
||||||
|
default:
|
||||||
|
return LoopMode.off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
|
|
||||||
import '../../core/error/failures.dart';
|
import '../../core/error/failures.dart';
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
import '../../domain/entities/playback_state.dart';
|
import '../../domain/entities/playback_state.dart';
|
||||||
import '../../domain/entities/shuffle_mode.dart';
|
import '../../domain/entities/shuffle_mode.dart';
|
||||||
import '../../domain/entities/song.dart';
|
import '../../domain/entities/song.dart';
|
||||||
|
@ -97,4 +98,10 @@ class AudioRepositoryImpl implements AudioRepository {
|
||||||
await _audioManager.removeQueueIndex(index);
|
await _audioManager.removeQueueIndex(index);
|
||||||
return const Right(null);
|
return const Right(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setLoopMode(LoopMode loopMode) async {
|
||||||
|
print('setLoopMode!');
|
||||||
|
await _audioManager.setLoopMode(loopMode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import 'package:path_provider/path_provider.dart';
|
||||||
import '../../core/error/failures.dart';
|
import '../../core/error/failures.dart';
|
||||||
import '../../domain/entities/album.dart';
|
import '../../domain/entities/album.dart';
|
||||||
import '../../domain/entities/artist.dart';
|
import '../../domain/entities/artist.dart';
|
||||||
|
import '../../domain/entities/loop_mode.dart';
|
||||||
import '../../domain/entities/song.dart';
|
import '../../domain/entities/song.dart';
|
||||||
import '../../domain/repositories/music_data_repository.dart';
|
import '../../domain/repositories/music_data_repository.dart';
|
||||||
import '../datasources/local_music_fetcher_contract.dart';
|
import '../datasources/local_music_fetcher_contract.dart';
|
||||||
|
@ -139,4 +140,7 @@ class MusicDataRepositoryImpl implements MusicDataRepository {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<int> get currentIndexStream => musicDataSource.currentIndexStream;
|
Stream<int> get currentIndexStream => musicDataSource.currentIndexStream;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Stream<LoopMode> get loopModeStream => musicDataSource.loopModeStream;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue