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

View File

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

View File

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

View File

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