Custom playlist support.

This commit is contained in:
stonega 2020-12-23 22:03:07 +08:00
parent 1d8db22dde
commit 963415ded4
4 changed files with 131 additions and 32 deletions

View File

@ -359,14 +359,15 @@ class __PlaylistButtonState extends State<_PlaylistButton> {
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0)),
),
child: Selector<AudioPlayerNotifier, Tuple3<bool, Playlist, int>>(
selector: (_, audio) =>
Tuple3(audio.playerRunning, audio.queue, audio.lastPositin),
child: Selector<AudioPlayerNotifier,
Tuple3<bool, EpisodeBrief, int>>(
selector: (_, audio) => Tuple3(
audio.playerRunning, audio.episode, audio.lastPositin),
builder: (_, data, __) => !_loadPlay
? SizedBox(
height: 8.0,
)
: data.item1 || data.item2.episodes.isEmpty
: data.item1 || data.item2 == null
? SizedBox(
height: 8.0,
)
@ -390,8 +391,8 @@ class __PlaylistButtonState extends State<_PlaylistButton> {
children: <Widget>[
CircleAvatar(
radius: 20,
backgroundImage: data
.item2.episodes.first.avatarImage),
backgroundImage:
data.item2.avatarImage),
Container(
height: 40.0,
width: 40.0,
@ -419,7 +420,7 @@ class __PlaylistButtonState extends State<_PlaylistButton> {
// TextStyle(color: Colors.white)
),
Text(
data.item2.episodes.first.title,
data.item2.title,
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.fade,

View File

@ -56,6 +56,7 @@ class _PlaylistHomeState extends State<PlaylistHome> {
@override
Widget build(BuildContext context) {
final s = context.s;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
systemNavigationBarIconBrightness:
@ -66,6 +67,12 @@ class _PlaylistHomeState extends State<PlaylistHome> {
child: Scaffold(
appBar: AppBar(
leading: CustomBackButton(),
title: Selector<AudioPlayerNotifier, EpisodeBrief>(
selector: (_, audio) => audio.episode,
builder: (_, data, __) {
return Text(data?.title ?? '', maxLines: 1);
},
),
backgroundColor: context.scaffoldBackgroundColor,
),
body: Column(
@ -126,23 +133,58 @@ class _PlaylistHomeState extends State<PlaylistHome> {
})
],
),
data.item4 != null
? Padding(
if (data.item2)
Selector<AudioPlayerNotifier,
Tuple3<bool, double, String>>(
selector: (_, audio) => Tuple3(
audio.buffering,
(audio.backgroundAudioDuration -
audio.backgroundAudioPosition) /
1000,
audio.remoteErrorMessage),
builder: (_, data, __) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20.0),
child: Text(data.item4.title, maxLines: 1),
)
: Center(),
horizontal: 10),
child: data.item3 != null
? Text(data.item3,
style: const TextStyle(
color: Color(0xFFFF0000)))
: data.item1
? Text(
s.buffering,
style: TextStyle(
color: context.accentColor),
)
: Text(
s.timeLeft((data.item2)
.toInt()
.toTime ??
''),
maxLines: 2,
),
);
},
)
],
)),
data.item3 != null
? ClipRRect(
borderRadius: BorderRadius.circular(10),
child: SizedBox(
width: 80,
height: 80,
child:
Image(image: data.item4.avatarImage)),
child: InkWell(
onTap: () {
if (running) {
context
.read<AudioPlayerNotifier>()
.playNext();
}
},
child: SizedBox(
width: 80,
height: 80,
child:
Image(image: data.item4.avatarImage)),
),
)
: Container(
decoration: BoxDecoration(
@ -214,11 +256,13 @@ class __QueueState extends State<_Queue> {
@override
Widget build(BuildContext context) {
final s = context.s;
return Selector<AudioPlayerNotifier, Tuple2<Playlist, bool>>(
selector: (_, audio) => Tuple2(audio.playlist, audio.playerRunning),
return Selector<AudioPlayerNotifier, Tuple3<Playlist, bool, EpisodeBrief>>(
selector: (_, audio) =>
Tuple3(audio.playlist, audio.playerRunning, audio.episode),
builder: (_, data, __) {
var episodes = data.item1.episodes.toSet().toList();
var queue = data.item1;
var running = data.item2;
return queue.name == 'Queue'
? ReorderableListView(
onReorder: (oldIndex, newIndex) {
@ -258,6 +302,8 @@ class __QueueState extends State<_Queue> {
itemBuilder: (context, index) {
final episode = queue.episodes[index];
final c = episode.backgroudColor(context);
final isPlaying =
data.item3 != null && data.item3 == episode;
return SizedBox(
height: 90.0,
child: Column(
@ -265,11 +311,15 @@ class __QueueState extends State<_Queue> {
children: <Widget>[
Expanded(
child: ListTile(
tileColor:
isPlaying ? context.primaryColorDark : null,
contentPadding: EdgeInsets.symmetric(vertical: 8),
onTap: () async {
await context
.read<AudioPlayerNotifier>()
.episodeLoad(episode);
if (!isPlaying) {
await context
.read<AudioPlayerNotifier>()
.loadEpisodeFromPlaylist(episode);
}
},
title: Container(
padding: EdgeInsets.fromLTRB(0, 5.0, 20.0, 5.0),
@ -284,7 +334,8 @@ class __QueueState extends State<_Queue> {
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.unfold_more, color: c),
// Icon(Icons.unfold_more, color: c),
SizedBox(width: 24),
CircleAvatar(
backgroundColor: c.withOpacity(0.5),
backgroundImage: episode.avatarImage),
@ -323,7 +374,18 @@ class __QueueState extends State<_Queue> {
],
),
),
//trailing: Icon(Icons.menu),
trailing: isPlaying && running
? Container(
height: 20,
width: 20,
margin:
EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
shape: BoxShape.circle,
),
child: WaveLoader(
color: context.accentColor))
: SizedBox(width: 1),
),
),
Divider(
@ -688,7 +750,14 @@ class __PlaylistsState extends State<_Playlists> {
style: context.textTheme.headline6,
),
Text('${queue.episodes.length} episodes'),
OutlinedButton(
TextButton(
style: OutlinedButton.styleFrom(
side: BorderSide(
color: context.primaryColorDark),
primary: context.accentColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(100)))),
onPressed: () {
context
.read<AudioPlayerNotifier>()
@ -738,11 +807,11 @@ class __PlaylistsState extends State<_Playlists> {
),
title: Text(data[index].name),
subtitle: Text(episodeList.isNotEmpty
? s.episode(data[index].episodeList.length)
? '${data[index].episodeList.length} episodes'
: '0 episode'),
trailing: IconButton(
splashRadius: 20,
icon: Icon(Icons.play_arrow),
icon: Icon(LineIcons.play_circle_solid, size: 30),
onPressed: () {
context
.read<AudioPlayerNotifier>()

View File

@ -269,6 +269,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
await _getAutoPlay();
var state = await _playerStateStorage.getPlayerState();
print(state.toString());
if (state[0] != '') {
_playlist = _playlists.firstWhere((p) => p.id == state[0],
orElse: () => _playlists.first);
@ -306,7 +307,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
Future<void> playlistLoad(Playlist playlist) async {
var p = playlist;
if(playlist.name != 'Queue') {
if (playlist.name != 'Queue') {
await updatePlaylist(p, updateEpisodes: true);
}
_playlist = p;
@ -337,6 +338,12 @@ class AudioPlayerNotifier extends ChangeNotifier {
final history = PlayHistory(_episode.title, _episode.enclosureUrl,
backgroundAudioPosition ~/ 1000, seekSliderValue);
await _dbHelper.saveHistory(history);
if (_playlist.name != 'Queue') {
AudioService.customAction('setIsQueue', true);
AudioService.customAction('changeQueue', [
for (var e in _queue.episodes) jsonEncode(e.toMediaItem().toJson())
]);
}
await AudioService.addQueueItemAt(episodeNew.toMediaItem(), 0);
if (startPosition > 0) {
await AudioService.seekTo(Duration(milliseconds: startPosition));
@ -361,6 +368,13 @@ class AudioPlayerNotifier extends ChangeNotifier {
}
}
Future<void> loadEpisodeFromPlaylist(EpisodeBrief episode) async {
if (_playlist.episodes.contains(episode)) {
var index = _playlist.episodes.indexOf(episode);
await AudioService.customAction('changeIndex', index);
}
}
Future<void> _startAudioService(playlist,
{int index = 0, int position = 0}) async {
_stopOnComplete = false;
@ -516,7 +530,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
_lastPostion ~/ 1000, _seekSliderValue);
await _dbHelper.saveHistory(history);
}
_episode = null;
//_episode = null;
}
});
@ -1019,7 +1033,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
_skipState = AudioProcessingState.skippingToNext;
_playing = false;
await _audioPlayer.stop();
AudioServiceBackground.sendCustomEvent(_queue.first.title);
AudioServiceBackground.sendCustomEvent(mediaItem.title);
if (_isQueue) {
if (_queue.length > 0) {
_queue.removeAt(0);
@ -1225,12 +1239,17 @@ class AudioPlayerTask extends BackgroundAudioTask {
break;
case 'changeQueue':
await _changeQueue(argument);
break;
case 'changeIndex':
await _changeIndex(argument);
break;
}
}
Future _changeQueue(List<dynamic> items) async {
var queue = [for (var i in items) MediaItem.fromJson(json.decode(i))];
await _audioPlayer.stop();
AudioServiceBackground.sendCustomEvent(mediaItem.title);
_queue.clear();
_queue.addAll(queue);
_index = 0;
@ -1242,6 +1261,17 @@ class AudioPlayerTask extends BackgroundAudioTask {
_playFromStart();
}
Future _changeIndex(int index) async {
await _audioPlayer.stop();
AudioServiceBackground.sendCustomEvent(mediaItem.title);
_index = index;
await AudioServiceBackground.setMediaItem(mediaItem);
await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax);
var duration = await _audioPlayer.durationFuture ?? Duration.zero;
AudioServiceBackground.setMediaItem(mediaItem.copyWith(duration: duration));
_playFromStart();
}
Future _setSkipSilence(bool boo) async {
await _audioPlayer.setSkipSilence(boo);
var duration = await _audioPlayer.durationFuture ?? Duration.zero;

View File

@ -61,7 +61,6 @@ class Playlist extends Equatable {
episodes.clear();
if (episodeList.isNotEmpty) {
for (var url in episodeList) {
print(url);
var episode = await _dbHelper.getRssItemWithUrl(url);
if (episode != null) episodes.add(episode);
}