From 963415ded49c71a0140fea7278ce4ba3ea92b921 Mon Sep 17 00:00:00 2001 From: stonega Date: Wed, 23 Dec 2020 22:03:07 +0800 Subject: [PATCH] Custom playlist support. --- lib/home/home.dart | 15 +++-- lib/playlists/playlist_home.dart | 111 +++++++++++++++++++++++++------ lib/state/audio_state.dart | 36 +++++++++- lib/type/playlist.dart | 1 - 4 files changed, 131 insertions(+), 32 deletions(-) diff --git a/lib/home/home.dart b/lib/home/home.dart index e7c0f64..7681375 100644 --- a/lib/home/home.dart +++ b/lib/home/home.dart @@ -359,14 +359,15 @@ class __PlaylistButtonState extends State<_PlaylistButton> { topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)), ), - child: Selector>( - selector: (_, audio) => - Tuple3(audio.playerRunning, audio.queue, audio.lastPositin), + child: Selector>( + 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: [ 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, diff --git a/lib/playlists/playlist_home.dart b/lib/playlists/playlist_home.dart index 6677dc1..92217c3 100644 --- a/lib/playlists/playlist_home.dart +++ b/lib/playlists/playlist_home.dart @@ -56,6 +56,7 @@ class _PlaylistHomeState extends State { @override Widget build(BuildContext context) { + final s = context.s; return AnnotatedRegion( value: SystemUiOverlayStyle( systemNavigationBarIconBrightness: @@ -66,6 +67,12 @@ class _PlaylistHomeState extends State { child: Scaffold( appBar: AppBar( leading: CustomBackButton(), + title: Selector( + 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 { }) ], ), - data.item4 != null - ? Padding( + if (data.item2) + Selector>( + 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() + .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>( - selector: (_, audio) => Tuple2(audio.playlist, audio.playerRunning), + return Selector>( + 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: [ Expanded( child: ListTile( + tileColor: + isPlaying ? context.primaryColorDark : null, contentPadding: EdgeInsets.symmetric(vertical: 8), onTap: () async { - await context - .read() - .episodeLoad(episode); + if (!isPlaying) { + await context + .read() + .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() @@ -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() diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index 2eee5b5..07f1a69 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -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 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 loadEpisodeFromPlaylist(EpisodeBrief episode) async { + if (_playlist.episodes.contains(episode)) { + var index = _playlist.episodes.indexOf(episode); + await AudioService.customAction('changeIndex', index); + } + } + Future _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 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; diff --git a/lib/type/playlist.dart b/lib/type/playlist.dart index 1a2e644..e846f2a 100644 --- a/lib/type/playlist.dart +++ b/lib/type/playlist.dart @@ -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); }