From 7312fdd10cd42cae30b95bbddc81ea0ffc5a743f Mon Sep 17 00:00:00 2001 From: stonega Date: Fri, 9 Apr 2021 22:23:15 +0800 Subject: [PATCH 1/7] Move audio service to one isolate branch. --- lib/state/audio_state.dart | 638 ++++++++++++++++--------------------- lib/type/episodebrief.dart | 2 +- pubspec.yaml | 21 +- 3 files changed, 284 insertions(+), 377 deletions(-) diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index a33967e..021a789 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -1,10 +1,10 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:math' as math; import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; import 'package:dio/dio.dart'; +import 'package:rxdart/rxdart.dart'; import 'package:flutter/foundation.dart'; import 'package:just_audio/just_audio.dart'; @@ -51,10 +51,6 @@ MediaControl rewindControl = MediaControl( action: MediaAction.rewind, ); -void _audioPlayerTaskEntrypoint() async { - AudioServiceBackground.run(() => AudioPlayerTask()); -} - /// Sleep timer mode. enum SleepTimerMode { endOfEpisode, timer, undefined } enum PlayerHeight { short, mid, tall } @@ -93,7 +89,7 @@ class AudioPlayerNotifier extends ChangeNotifier { Playlist get _queue => _playlists.first; /// Player state. - AudioProcessingState _audioState = AudioProcessingState.none; + AudioProcessingState _audioState = AudioProcessingState.loading; /// Player playing. bool _playing = false; @@ -178,17 +174,23 @@ class AudioPlayerNotifier extends ChangeNotifier { // Tmep episode list, playing from search result List _playFromSearchList = []; - @override - void addListener(VoidCallback listener) { - super.addListener(listener); - _initAudioData(); - AudioService.connect(); - } + AudioHandler _audioHandler; + AudioSession _session; @override - void dispose() async { - await AudioService.disconnect(); - super.dispose(); + void addListener(VoidCallback listener) async { + super.addListener(listener); + _initAudioData(); + _audioHandler = await AudioService.init( + builder: () => CustomAudioHandler(), + config: AudioServiceConfig( + androidNotificationChannelName: 'Tsacdop', + androidNotificationIcon: 'drawable/ic_notification', + androidEnableQueue: true, + androidStopForegroundOnPause: true, + fastForwardInterval: Duration(seconds: _fastForwardSeconds), + rewindInterval: Duration(seconds: _rewindSeconds)), + ); } /// Audio playing state. @@ -326,7 +328,7 @@ class AudioPlayerNotifier extends ChangeNotifier { if (_episode == null || !_playlist.episodes.contains(_episode)) { _episode = _playlist.isNotEmpty ? _playlist.episodes.first : null; } - _audioState = AudioProcessingState.none; + _audioState = AudioProcessingState.loading; _backgroundAudioDuration = 0; _backgroundAudioPosition = 0; _seekSliderValue = 0; @@ -347,15 +349,17 @@ class AudioPlayerNotifier extends ChangeNotifier { notifyListeners(); if (playlist.isNotEmpty) { if (playerRunning) { - AudioService.customAction('setIsQueue', playlist.name == 'Queue'); - AudioService.customAction('changeQueue', - [for (var e in p.episodes) jsonEncode(e.toMediaItem().toJson())]); + _audioHandler + .customAction('setIsQueue', {'isQueue': playlist.name == 'Queue'}); + _audioHandler.customAction('changeQueue', { + 'queue': [for (var e in p.episodes) e.toMediaItem()] + }); } else { _backgroundAudioDuration = 0; _backgroundAudioPosition = 0; _seekSliderValue = 0; _episode = playlist.episodes.first; - _audioState = AudioProcessingState.none; + _audioState = AudioProcessingState.loading; _playerRunning = true; notifyListeners(); _startAudioService(_playlist, position: 0, index: 0); @@ -384,15 +388,16 @@ class AudioPlayerNotifier extends ChangeNotifier { _queue.addToPlayListAt(episodeNew, 0); await updatePlaylist(_queue, updateEpisodes: !fromSearch); if (!_playlist.isQueue) { - AudioService.customAction('setIsQueue', true); - AudioService.customAction('changeQueue', [ - for (var e in _queue.episodes) jsonEncode(e.toMediaItem().toJson()) - ]); + _audioHandler.customAction('setIsQueue', {'isQueue': true}); + _audioHandler.customAction('changeQueue', { + 'queue': [for (var e in _queue.episodes) e.toMediaItem()] + }); _playlist = _queue; } - await AudioService.addQueueItemAt(episodeNew.toMediaItem(), 0); + await _audioHandler.customAction('addQueueItemAt', + {'mediaItem': episodeNew.toMediaItem(), 'index': 0}); if (startPosition > 0) { - await AudioService.seekTo(Duration(milliseconds: startPosition)); + await _audioHandler.seek(Duration(milliseconds: startPosition)); } _remoteErrorMessage = null; notifyListeners(); @@ -417,7 +422,7 @@ 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); + await _audioHandler.customAction('changeIndex', {'index': index}); } } @@ -427,11 +432,6 @@ class AudioPlayerNotifier extends ChangeNotifier { _sleepTimerMode = SleepTimerMode.undefined; _switchValue = 0; - /// Connect to audio service. - if (!AudioService.connected) { - await AudioService.connect(); - } - /// Get fastword and rewind seconds. _fastForwardSeconds = await _fastForwardSecondsStorage.getInt(defaultValue: 30); @@ -442,25 +442,15 @@ class AudioPlayerNotifier extends ChangeNotifier { await _markListenedAfterSkipStorage.getBool(defaultValue: false); /// Start audio service. - await AudioService.start( - backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint, - params: {'index': index, 'isQueue': playlist.name == 'Queue'}, - androidNotificationChannelName: 'Tsacdop', - androidNotificationColor: 0xFF4d91be, - androidNotificationIcon: 'drawable/ic_notification', - androidEnableQueue: true, - androidStopForegroundOnPause: true, - fastForwardInterval: Duration(seconds: _fastForwardSeconds), - rewindInterval: Duration(seconds: _rewindSeconds)); //Check autoplay setting, if true only add one episode, else add playlist. await _getAutoPlay(); if (_autoPlay) { for (var episode in playlist.episodes) { - await AudioService.addQueueItem(episode.toMediaItem()); + await _audioHandler.addQueueItem(episode.toMediaItem()); } } else { - await AudioService.addQueueItem(playlist.episodes[index].toMediaItem()); + await _audioHandler.addQueueItem(playlist.episodes[index].toMediaItem()); } //Check auto sleep timer setting await _getAutoSleepTimer(); @@ -483,23 +473,22 @@ class AudioPlayerNotifier extends ChangeNotifier { /// Set player speed. if (_currentSpeed != 1.0) { - await AudioService.customAction('setSpeed', _currentSpeed); + await _audioHandler.customAction('setSpeed', {'speed': _currentSpeed}); } /// Set slipsilence. if (_skipSilence) { - await AudioService.customAction('setSkipSilence', skipSilence); + // await _audioHandler.customAction('setSkipSilence', {'': skipSilence}); } /// Set boostValome. - if (_boostVolume) { - await AudioService.customAction( - 'setBoostVolume', [_boostVolume, _volumeGain]); - } + // if (_boostVolume) { + // await _audioHandler.customAction( + // 'setBoostVolume', [_boostVolume, _volumeGain]); + // } - await AudioService.play(); - - AudioService.currentMediaItemStream + await _audioHandler.play(); + _audioHandler.mediaItem .where((event) => event != null) .listen((item) async { var episode = await _dbHelper.getRssItemWithMediaId(item.id); @@ -522,7 +511,7 @@ class AudioPlayerNotifier extends ChangeNotifier { AudioService.skipToNext(); } }); - AudioService.playbackStateStream + _audioHandler.playbackState .distinct() .where((event) => event != null) .listen((event) async { @@ -530,9 +519,9 @@ class AudioPlayerNotifier extends ChangeNotifier { _audioState = event.processingState; _playing = event?.playing; _currentSpeed = event.speed; - _currentPosition = event.currentPosition.inMilliseconds ?? 0; + _currentPosition = event.position.inMilliseconds ?? 0; - if (_audioState == AudioProcessingState.stopped) { + if (_audioState == AudioProcessingState.completed) { if (_switchValue > 0) _switchValue = 0; } @@ -548,7 +537,7 @@ class AudioPlayerNotifier extends ChangeNotifier { notifyListeners(); }); - AudioService.customEventStream.distinct().listen((event) async { + _audioHandler.customEvent.distinct().listen((event) async { if (event is String) { if (_playlist.isQueue && _queue.isNotEmpty && @@ -615,7 +604,7 @@ class AudioPlayerNotifier extends ChangeNotifier { } notifyListeners(); } - if (_audioState == AudioProcessingState.stopped) { + if (_audioState == AudioProcessingState.completed) { timer.cancel(); } }); @@ -629,7 +618,7 @@ class AudioPlayerNotifier extends ChangeNotifier { } if (!_queue.episodes.contains(episodeNew)) { if (playerRunning && _playlist.isQueue) { - await AudioService.addQueueItem(episodeNew.toMediaItem()); + await _audioHandler.addQueueItem(episodeNew.toMediaItem()); } if (_playlist.isQueue && _queue.isEmpty) _episode = episodeNew; _queue.addToPlayList(episodeNew); @@ -643,7 +632,8 @@ class AudioPlayerNotifier extends ChangeNotifier { await _dbHelper.removeEpisodeNewMark(episodeNew.enclosureUrl); } if (_playerRunning && _playlist.isQueue) { - await AudioService.addQueueItemAt(episodeNew.toMediaItem(), index); + await _audioHandler.customAction('addQueueItemAt', + {'mediaItem': episodeNew.toMediaItem(), 'index': index}); } _queue.addToPlayListAt(episodeNew, index); await updatePlaylist(_queue, updateEpisodes: false); @@ -675,7 +665,7 @@ class AudioPlayerNotifier extends ChangeNotifier { var episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); _playlist.updateEpisode(episodeNew); if (_playerRunning) { - await AudioService.updateMediaItem(episodeNew.toMediaItem()); + await _audioHandler.updateMediaItem(episodeNew.toMediaItem()); } } } @@ -683,7 +673,7 @@ class AudioPlayerNotifier extends ChangeNotifier { Future delFromPlaylist(EpisodeBrief episode) async { var episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); if (playerRunning && _playlist.isQueue) { - await AudioService.removeQueueItem(episodeNew.toMediaItem()); + await _audioHandler.removeQueueItem(episodeNew.toMediaItem()); } var index = _queue.delFromPlaylist(episodeNew); if (index == 0) { @@ -702,8 +692,9 @@ class AudioPlayerNotifier extends ChangeNotifier { _queue.addToPlayListAt(episode, newIndex); updatePlaylist(_queue, updateEpisodes: false); if (playerRunning && _playlist.name == 'Queue') { - await AudioService.removeQueueItem(episode.toMediaItem()); - await AudioService.addQueueItemAt(episode.toMediaItem(), newIndex); + await _audioHandler.removeQueueItem(episode.toMediaItem()); + await _audioHandler.customAction('addQueueItemAt', + {'mediaItem': episode.toMediaItem(), 'index': newIndex}); } if (newIndex == 0) { _lastPosition = 0; @@ -715,7 +706,8 @@ class AudioPlayerNotifier extends ChangeNotifier { await delFromPlaylist(episode); final episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); if (_playerRunning && _playlist.isQueue) { - await AudioService.addQueueItemAt(episodeNew.toMediaItem(), 1); + await _audioHandler.customAction( + '', {'mediaItem': episodeNew.toMediaItem(), 'index': 1}); _queue.addToPlayListAt(episode, 1, existed: false); } else { _queue.addToPlayListAt(episode, 0, existed: false); @@ -756,7 +748,7 @@ class AudioPlayerNotifier extends ChangeNotifier { for (var e in episodes) { playlist.addToPlayList(e); if (playerRunning && playlist == _playlist) { - AudioService.addQueueItem(e.toMediaItem()); + _audioHandler.addQueueItem(e.toMediaItem()); } } updatePlaylist(playlist, updateEpisodes: false); @@ -767,7 +759,7 @@ class AudioPlayerNotifier extends ChangeNotifier { for (var e in episodes) { playlist.delFromPlaylist(e); if (playerRunning && playlist == _playlist) { - AudioService.removeQueueItem(e.toMediaItem()); + _audioHandler.removeQueueItem(e.toMediaItem()); } } updatePlaylist(playlist, updateEpisodes: false); @@ -781,8 +773,9 @@ class AudioPlayerNotifier extends ChangeNotifier { if (newIndex > oldIndex) { newIndex -= 1; } - await AudioService.removeQueueItem(episode.toMediaItem()); - await AudioService.addQueueItemAt(episode.toMediaItem(), newIndex); + await _audioHandler.removeQueueItem(episode.toMediaItem()); + await _audioHandler.customAction('addQueueItemAt', + {'mediaItem': episode.toMediaItem(), 'index': newIndex}); } updatePlaylist(playlist, updateEpisodes: false); } @@ -836,88 +829,85 @@ class AudioPlayerNotifier extends ChangeNotifier { /// Audio control. Future pauseAduio() async { - await AudioService.pause(); + await _audioHandler.pause(); } Future resumeAudio() async { _remoteErrorMessage = null; notifyListeners(); - if (_audioState != AudioProcessingState.connecting && - _audioState != AudioProcessingState.none) { - AudioService.play(); + if (_audioState != AudioProcessingState.loading) { + _audioHandler.play(); } } Future playNext() async { _remoteErrorMessage = null; - await AudioService.skipToNext(); + await _audioHandler.skipToNext(); notifyListeners(); } Future forwardAudio(int s) async { var pos = _backgroundAudioPosition + s * 1000; - await AudioService.seekTo(Duration(milliseconds: pos)); + await _audioHandler.seek(Duration(milliseconds: pos)); } Future fastForward() async { - await AudioService.fastForward(); + await _audioHandler.fastForward(); } Future rewind() async { - await AudioService.rewind(); + await _audioHandler.rewind(); } Future seekTo(int position) async { - if (_audioState != AudioProcessingState.connecting && - _audioState != AudioProcessingState.none) { - await AudioService.seekTo(Duration(milliseconds: position)); + if (_audioState != AudioProcessingState.loading) { + await _audioHandler.seek(Duration(milliseconds: position)); } } Future sliderSeek(double val) async { - if (_audioState != AudioProcessingState.connecting && - _audioState != AudioProcessingState.none) { + if (_audioState != AudioProcessingState.loading) { _noSlide = false; _seekSliderValue = val; notifyListeners(); _currentPosition = (val * _backgroundAudioDuration).toInt(); - await AudioService.seekTo(Duration(milliseconds: _currentPosition)); + await _audioHandler.seek(Duration(milliseconds: _currentPosition)); _noSlide = true; } } /// Set player speed. Future setSpeed(double speed) async { - await AudioService.customAction('setSpeed', speed); + await _audioHandler.customAction('setSpeed', {'speed': speed}); _currentSpeed = speed; await _speedStorage.saveDouble(_currentSpeed); notifyListeners(); } /// Set skip silence. - Future setSkipSilence({@required bool skipSilence}) async { - await AudioService.customAction('setSkipSilence', skipSilence); - _skipSilence = skipSilence; - await _skipSilenceStorage.saveBool(_skipSilence); - notifyListeners(); - } + // Future setSkipSilence({@required bool skipSilence}) async { + // await AudioService.customAction('setSkipSilence', skipSilence); + // _skipSilence = skipSilence; + // await _skipSilenceStorage.saveBool(_skipSilence); + // notifyListeners(); + // } set setVolumeGain(int volumeGain) { _volumeGain = volumeGain; if (_playerRunning && _boostVolume) { - setBoostVolume(boostVolume: _boostVolume, gain: _volumeGain); + // setBoostVolume(boostVolume: _boostVolume, gain: _volumeGain); } notifyListeners(); _volumeGainStorage.saveInt(volumeGain); } - Future setBoostVolume({@required bool boostVolume, int gain}) async { - await AudioService.customAction( - 'setBoostVolume', [boostVolume, _volumeGain]); - _boostVolume = boostVolume; - notifyListeners(); - await _boostVolumeStorage.saveBool(boostVolume); - } + // Future setBoostVolume({@required bool boostVolume, int gain}) async { + // await _audioHandler.customAction( + // 'setBoostVolume', [boostVolume, _volumeGain]); + // _boostVolume = boostVolume; + // notifyListeners(); + // await _boostVolumeStorage.saveBool(boostVolume); + // } //Set sleep timer void sleepTimer(int mins) { @@ -940,7 +930,7 @@ class AudioPlayerNotifier extends ChangeNotifier { _startSleepTimer = false; _switchValue = 0; if (_playerRunning) { - AudioService.stop(); + _audioHandler.stop(); } notifyListeners(); // AudioService.disconnect(); @@ -950,7 +940,7 @@ class AudioPlayerNotifier extends ChangeNotifier { _switchValue = 1; notifyListeners(); if (_queue.episodes.length > 1 && _autoPlay) { - AudioService.customAction('stopAtEnd'); + _audioHandler.customAction('stopAtEnd', {}); } } } @@ -969,7 +959,7 @@ class AudioPlayerNotifier extends ChangeNotifier { _switchValue = 0; notifyListeners(); } else if (_sleepTimerMode == SleepTimerMode.endOfEpisode) { - AudioService.customAction('cancelStopAtEnd'); + _audioHandler.customAction('cancelStopAtEnd', {}); _switchValue = 0; _stopOnComplete = false; notifyListeners(); @@ -977,81 +967,62 @@ class AudioPlayerNotifier extends ChangeNotifier { } } -class AudioPlayerTask extends BackgroundAudioTask { +class CustomAudioHandler extends BaseAudioHandler + with QueueHandler, SeekHandler { final cacheStorage = KeyValueStorage(cacheMaxKey); final layoutStorage = KeyValueStorage(notificationLayoutKey); - final List _queue = []; - final AudioPlayer _audioPlayer = AudioPlayer(); + final AudioPlayer _player = AudioPlayer(); AudioSession _session; - AudioProcessingState _skipState; bool _playing; bool _interrupted = false; bool _stopAtEnd; int _cacheMax; - int _index = 0; bool _isQueue; - bool get hasNext => _queue.length > 0; + int _index = 0; - MediaItem get mediaItem => hasNext ? _queue[_index] : null; + bool get hasNext => queue.value.length > 0; + MediaItem get currentMediaItem => mediaItem.value; + bool get playing => playbackState.value.playing; - StreamSubscription _playerStateSubscription; - StreamSubscription _eventSubscription; + // MediaItem get _currentMediaItem => hasNext ? _queue[_index] : null; + BehaviorSubject> customEvent; - @override - Future onStart(Map params) async { - _stopAtEnd = false; - _session = await AudioSession.instance; - _index = params['index'] ?? 0; - _isQueue = params['isQueue'] ?? true; - await _session.configure(AudioSessionConfiguration.speech()); - _handleInterruption(_session); - _playerStateSubscription = _audioPlayer.playbackStateStream - .where((state) => state == AudioPlaybackState.completed) - .listen((state) { - _handlePlaybackCompleted(); - }); - - _eventSubscription = _audioPlayer.playbackEventStream.listen((event) { - if (event.playbackError != null) { - _playing = false; - _setState(processingState: _skipState ?? AudioProcessingState.error); - } - final bufferingState = - event.buffering ? AudioProcessingState.buffering : null; - switch (event.state) { - case AudioPlaybackState.paused: - _setState( - processingState: bufferingState ?? AudioProcessingState.ready, - position: event.position, - ); - break; - case AudioPlaybackState.playing: - _setState( - processingState: bufferingState ?? AudioProcessingState.ready, - position: event.position, - ); - break; - case AudioPlaybackState.connecting: - _setState( - processingState: _skipState ?? AudioProcessingState.connecting, - position: event.position, - ); - break; - default: - break; - } + CustomAudioHandler() { + _player.currentIndexStream + .listen((index) => mediaItem.add(queue.value[index])); + _player.playbackEventStream.listen((event) { + playbackState.add(playbackState.value.copyWith( + controls: [ + MediaControl.skipToPrevious, + playing ? MediaControl.pause : MediaControl.play, + MediaControl.skipToNext, + ], + androidCompactActionIndices: [0, 1, 3], + systemActions: { + MediaAction.seek, + MediaAction.seekForward, + MediaAction.seekBackward, + }, + processingState: { + ProcessingState.idle: AudioProcessingState.idle, + ProcessingState.loading: AudioProcessingState.loading, + ProcessingState.buffering: AudioProcessingState.buffering, + ProcessingState.ready: AudioProcessingState.ready, + ProcessingState.completed: AudioProcessingState.completed, + }[_player.processingState], + playing: _player.playing, + updatePosition: _player.position, + bufferedPosition: _player.bufferedPosition, + speed: _player.speed, + )); }); + _configureSession(); } - void _handlePlaybackCompleted() async { - if (hasNext) { - onSkipToNext(); - } else { - _audioPlayer.stop(); - _queue.removeAt(0); - await AudioServiceBackground.setQueue(_queue); - onStop(); - } + Future _configureSession() async { + _session = await AudioSession.instance; + await _session.configure(AudioSessionConfiguration.speech()); + _handleInterruption(_session); } void _handleInterruption(AudioSession session) async { @@ -1060,19 +1031,19 @@ class AudioPlayerTask extends BackgroundAudioTask { switch (event.type) { case AudioInterruptionType.pause: if (_playing) { - onPause(); + pause(); _interrupted = true; } break; case AudioInterruptionType.duck: if (_playing) { - onPause(); + pause(); _interrupted = true; } break; case AudioInterruptionType.unknown: if (_playing) { - onPause(); + pause(); _interrupted = true; } break; @@ -1081,12 +1052,12 @@ class AudioPlayerTask extends BackgroundAudioTask { switch (event.type) { case AudioInterruptionType.pause: if (!_playing && _interrupted) { - onPlay(); + play(); } break; case AudioInterruptionType.duck: if (!_playing && _interrupted) { - onPlay(); + play(); } break; case AudioInterruptionType.unknown: @@ -1096,212 +1067,149 @@ class AudioPlayerTask extends BackgroundAudioTask { } }); session.becomingNoisyEventStream.listen((_) { - if (_playing) onPause(); + if (_playing) pause(); }); } + void _handlePlaybackCompleted() async { + if (hasNext) { + skipToNext(); + } else { + _player.stop(); + removeQueueItemAt(0); + stop(); + } + } + void playPause() { - if (AudioServiceBackground.state.playing) { - onPause(); + if (playbackState.value.playing) { + pause(); } else { - onPlay(); + play(); } } - @override - Future onSkipToNext() async { - _skipState = AudioProcessingState.skippingToNext; - _playing = false; - await _audioPlayer.stop(); - AudioServiceBackground.sendCustomEvent(mediaItem.title); + Future skipToNext() async { + customEvent.add({'mediaItem': currentMediaItem.title}); if (_isQueue) { - if (_queue.length > 0) { - _queue.removeAt(0); - } - await AudioServiceBackground.setQueue(_queue); - } else { - if (_index == _queue.length - 1) { - _index = 0; - } else { - _index += 1; + if (queue.value.length > 0) { + removeQueueItemAt(0); } } - - if (_queue.length == 0 || _stopAtEnd) { - _skipState = null; + if (queue.value.length == 0 || _stopAtEnd) { await Future.delayed(Duration(milliseconds: 200)); - await onStop(); + await stop(); } else { - // await AudioServiceBackground.setQueue(_queue); - await AudioServiceBackground.setMediaItem(mediaItem); - await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax); - var duration = await _audioPlayer.durationFuture; + super.skipToNext(); + var duration = await _player.durationFuture; if (duration != null) { - await AudioServiceBackground.setMediaItem( - mediaItem.copyWith(duration: duration)); + mediaItem.add(currentMediaItem.copyWith(duration: duration)); } - _skipState = null; - _playFromStart(); } } - @override - Future onPlay() async { - if (_skipState == null) { - if (_playing == null) { - _playing = true; - _cacheMax = await cacheStorage.getInt( - defaultValue: (200 * 1024 * 1024).toInt()); - if (_cacheMax == 0) { - await cacheStorage.saveInt((200 * 1024 * 1024).toInt()); - _cacheMax = 200 * 1024 * 1024; - } - await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax); - var duration = await _audioPlayer.durationFuture; - if (duration != null) { - await AudioServiceBackground.setMediaItem( - mediaItem.copyWith(duration: duration)); - } - _playFromStart(); - } else { - _playing = true; - _session.setActive(true); - if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting || - _audioPlayer.playbackEvent.state != AudioPlaybackState.none) { - await _audioPlayer.play(); - await _seekRelative(Duration(seconds: -3)); - } + Future play() async { + if (playing == null) { + // _cacheMax = await cacheStorage.getInt( + // defaultValue: (200 * 1024 * 1024).toInt()); + // if (_cacheMax == 0) { + // await cacheStorage.saveInt((200 * 1024 * 1024).toInt()); + // _cacheMax = 200 * 1024 * 1024; + // } + await _player.setUrl(queue.value[_index].id); + var duration = await _player.durationFuture; + if (duration != null) { + mediaItem.add(currentMediaItem.copyWith(duration: duration)); } + _playFromStart(); + } else { + _session.setActive(true); + await _player.play(); + await _seekRelative(Duration(seconds: -3)); } } Future _playFromStart() async { _playing = true; _session.setActive(true); - if (mediaItem.extras['skipSecondsStart'] > 0 || - mediaItem.extras['skipSecondsEnd'] > 0) { - _audioPlayer - .seek(Duration(seconds: mediaItem.extras['skipSecondsStart'])); + if (currentMediaItem.extras['skipSecondsStart'] > 0 || + currentMediaItem.extras['skipSecondsEnd'] > 0) { + _player + .seek(Duration(seconds: mediaItem.value.extras['skipSecondsStart'])); } - if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting || - _audioPlayer.playbackEvent.state != AudioPlaybackState.none) { - try { - _audioPlayer.play(); - } catch (e) { - _setState(processingState: AudioProcessingState.error); - } + try { + _player.play(); + } catch (e) { + playbackState.add(playbackState.value + .copyWith(processingState: AudioProcessingState.error)); } } - @override - Future onPause() async { - if (_skipState == null) { - if (_playing == null) { - } else if (_playing) { - _playing = false; - _audioPlayer.pause(); - } - } + Future pause() async { + _player.pause(); } - @override - Future onSeekTo(Duration position) async { - if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting || - _audioPlayer.playbackEvent.state != AudioPlaybackState.none) { - await _audioPlayer.seek(position); - } + Future seekTo(Duration position) async { + await _player.seek(position); } - @override Future onClick(MediaButton button) async { switch (button) { case MediaButton.media: - if (AudioServiceBackground.state?.playing == true) { - await onPause(); + if (playing) { + await pause(); } else { - await onPlay(); + await play(); } break; case MediaButton.next: - await onFastForward(); + await fastForward(); break; case MediaButton.previous: - await onRewind(); + await rewind(); break; } } Future _seekRelative(Duration offset) async { - var newPosition = _audioPlayer.playbackEvent.position + offset; + var newPosition = playbackState.value.position + offset; if (newPosition < Duration.zero) newPosition = Duration.zero; - onSeekTo(newPosition); + seekTo(newPosition); } - @override - Future onStop() async { - await _audioPlayer.stop(); - await _audioPlayer.dispose(); - _playing = false; - _playerStateSubscription.cancel(); - _eventSubscription.cancel(); - await _setState(processingState: AudioProcessingState.none); - AudioServiceBackground.sendCustomEvent({'playerRunning': false}); - await super.onStop(); + Future stop() async { + await _player.stop(); + await _player.dispose(); + playbackState.add(playbackState.value + .copyWith(processingState: AudioProcessingState.loading)); + customEvent.add({'playerRunning': false}); + await super.stop(); } - @override - Future onTaskRemoved() async { - await onStop(); + Future taskRemoved() async { + await stop(); } - @override - Future onAddQueueItem(MediaItem mediaItem) async { - _queue.add(mediaItem); - await AudioServiceBackground.setQueue(_queue); - } - - @override - Future onRemoveQueueItem(MediaItem mediaItem) async { - var index = _queue.indexOf(mediaItem); - if (index < _index) _index -= 1; - _queue.removeWhere((item) => item.id == mediaItem.id); - await AudioServiceBackground.setQueue(_queue); - } - - @override - Future onAddQueueItemAt(MediaItem mediaItem, int index) async { + Future _addQueueItemAt(MediaItem item, int index) async { if (index == 0 && _isQueue) { - await _audioPlayer.stop(); - _queue.removeAt(0); - _queue.removeWhere((item) => item.id == mediaItem.id); - _queue.insert(0, mediaItem); - await AudioServiceBackground.setQueue(_queue); - await AudioServiceBackground.setMediaItem(mediaItem); - await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax); - var duration = await _audioPlayer.durationFuture ?? Duration.zero; - AudioServiceBackground.setMediaItem( - mediaItem.copyWith(duration: duration)); + await _player.stop(); + queue.add(queue.value..removeAt(0)); + queue.add(queue.value..removeWhere((i) => i.id == item.id)); + queue.add(queue.value..insert(index, item)); + // await _player.setUrl(mediaItem.id, cacheMax: _cacheMax); + await _player.setUrl(item.id); + mediaItem.add(item); + var duration = await _player.durationFuture ?? Duration.zero; + mediaItem.add(mediaItem.value.copyWith(duration: duration)); _playFromStart(); //onPlay(); } else { - _queue.insert(index, mediaItem); - //if (index < _index) _index += 1; - await AudioServiceBackground.setQueue(_queue); + queue.add(queue.value..insert(index, item)); } } @override - Future onFastForward() async { - await _seekRelative(fastForwardInterval); - } - - @override - Future onRewind() async { - await _seekRelative(-rewindInterval); - } - - @override - Future onCustomAction(funtion, argument) async { + Future customAction(funtion, argument) async { switch (funtion) { case 'stopAtEnd': _stopAtEnd = true; @@ -1310,86 +1218,84 @@ class AudioPlayerTask extends BackgroundAudioTask { _stopAtEnd = false; break; case 'setSpeed': - await _audioPlayer.setSpeed(argument); + await _player.setSpeed(argument['speed']); break; case 'setSkipSilence': - await _setSkipSilence(argument); + // await _skipSilence(argument); break; case 'setBoostVolume': - await _setBoostVolume(argument[0], argument[1]); + // await _setBoostVolume(argument[0], argument[1]); break; case 'setIsQueue': - _isQueue = argument; + _isQueue = argument['isQueue']; break; case 'changeQueue': - await _changeQueue(argument); + await _changeQueue(argument['queue']); break; case 'changeIndex': - await _changeIndex(argument); + await _changeIndex(argument['index']); break; + case 'addQueueItemAt': + await _addQueueItemAt(argument['mediaItem'], argument['index']); } } - 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); + Future _changeQueue(List newQueue) async { + await _player.stop(); + customEvent.add({'mediaItem': currentMediaItem.title}); + queue.add(newQueue); _index = 0; - await AudioServiceBackground.setQueue(_queue); - await AudioServiceBackground.setMediaItem(mediaItem); - await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax); - var duration = await _audioPlayer.durationFuture ?? Duration.zero; - AudioServiceBackground.setMediaItem(mediaItem.copyWith(duration: duration)); + await _player.setUrl(currentMediaItem.id); + var duration = await _player.durationFuture ?? Duration.zero; + mediaItem.add(currentMediaItem.copyWith(duration: duration)); _playFromStart(); } Future _changeIndex(int index) async { - await _audioPlayer.stop(); - AudioServiceBackground.sendCustomEvent(mediaItem.title); + await _player.stop(); + customEvent.add({'mediaItem': currentMediaItem.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)); + mediaItem.add(currentMediaItem); + await _player.setUrl(currentMediaItem.id); + var duration = await _player.durationFuture ?? Duration.zero; + mediaItem.add(currentMediaItem.copyWith(duration: duration)); _playFromStart(); } - Future _setSkipSilence(bool boo) async { - await _audioPlayer.setSkipSilence(boo); - var duration = await _audioPlayer.durationFuture ?? Duration.zero; - AudioServiceBackground.setMediaItem(mediaItem.copyWith(duration: duration)); - } + // Future _setSkipSilence(bool boo) async { + // await _audioPlayer.setSkipSilence(boo); + // var duration = await _audioPlayer.durationFuture ?? Duration.zero; + // AudioServiceBackground.setMediaItem(mediaItem.copyWith(duration: duration)); + // } - Future _setBoostVolume(bool boo, int gain) async { - await _audioPlayer.setBoostVolume(boo, gain: gain); - } + // Future _setBoostVolume(bool boo, int gain) async { + // await _audioPlayer.setBoostVolume(boo, gain: gain); + // } - Future _setState({ - AudioProcessingState processingState, - Duration position, - Duration bufferedPosition, - }) async { - if (position == null) { - position = _audioPlayer.playbackEvent.position; - } - final index = await layoutStorage.getInt(defaultValue: 0); - await AudioServiceBackground.setState( - controls: _getControls(index), - systemActions: [ - MediaAction.seekTo, - MediaAction.seekForward, - MediaAction.seekBackward, - ], - processingState: - processingState ?? AudioServiceBackground.state.processingState, - playing: _playing ?? false, - position: position, - bufferedPosition: bufferedPosition ?? position, - speed: _audioPlayer.speed, - ); - } + // Future _setState({ + // AudioProcessingState processingState, + // Duration position, + // Duration bufferedPosition, + // }) async { + // if (position == null) { + // position = _player.playbackEvent.position; + // } + // final index = await layoutStorage.getInt(defaultValue: 0); + // await playbackState.add( + // controls: _getControls(index), + // systemActions: [ + // MediaAction.seekTo, + // MediaAction.seekForward, + // MediaAction.seekBackward, + // ], + // processingState: + // processingState ?? AudioServiceBackground.state.processingState, + // playing: _playing ?? false, + // position: position, + // bufferedPosition: bufferedPosition ?? position, + // speed: _audioPlayer.speed, + // ); + // } List _getControls(int index) { switch (index) { diff --git a/lib/type/episodebrief.dart b/lib/type/episodebrief.dart index 31a6329..48d75e7 100644 --- a/lib/type/episodebrief.dart +++ b/lib/type/episodebrief.dart @@ -56,7 +56,7 @@ class EpisodeBrief extends Equatable { artist: feedTitle, album: feedTitle, duration: Duration.zero, - artUri: imagePath == '' ? episodeImage : 'file://$imagePath', + artUri: Uri.parse(imagePath == '' ? episodeImage : 'file://$imagePath'), extras: { 'skipSecondsStart': skipSecondsStart, 'skipSecondsEnd': skipSecondsEnd diff --git a/pubspec.yaml b/pubspec.yaml index 4936b5b..6208f27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter_localizations: sdk: flutter auto_animated: ^3.0.0 - audio_session: ^0.0.9 + audio_session: ^0.1.0 cached_network_image: ^2.5.1 color_thief_flutter: ^1.0.2 confetti: ^0.5.5 @@ -44,22 +44,21 @@ dependencies: json_annotation: ^3.1.1 permission_handler: ^5.0.1 provider: ^4.3.2 - rxdart: ^0.24.1 - sqflite: ^1.3.2+1 + sqflite: ^2.0.0+2 state_notifier: ^0.6.0 tuple: ^1.0.3 url_launcher: ^5.7.10 workmanager: ^0.2.3 wc_flutter_share: ^0.2.2 + just_audio: ^0.7.3 + audio_service: + git: + url: https://github.com/ryanheise/audio_service.git + ref: one-isolate + path: audio_service flutter_downloader: git: url: https://github.com/stonega/flutter_downloader.git - audio_service: - git: - url: https://github.com/stonega/audio_service.git - just_audio: - git: - url: https://github.com/stonega/just_audio.git focused_menu: git: url: https://github.com/stonega/focused_menu.git @@ -73,8 +72,10 @@ dependency_overrides: cookie_jar: ^2.0.0 uuid: ^3.0.1 http: ^0.13.0 - flutter_cache_manager: ^2.1.2 + flutter_cache_manager: ^3.0.1 + rxdart: ^0.26.0 shared_preferences: 2.0.0 + plugin_platform_interface: ^1.0.1 convert: ^3.0.0 xml: ^5.0.2 linkify: From 7474afd7b8854ad6b915a1e210043e8c678020af Mon Sep 17 00:00:00 2001 From: stonega Date: Sat, 1 May 2021 14:17:56 +0800 Subject: [PATCH 2/7] Migrate to one isolate audio service. --- .../gradle/wrapper/gradle-wrapper.properties | 2 +- lib/home/audioplayer.dart | 18 +- lib/state/audio_state.dart | 325 ++++++++++-------- pubspec.yaml | 50 +-- 4 files changed, 214 insertions(+), 181 deletions(-) diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index dc10de9..fd9dc4d 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -4,4 +4,4 @@ distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip -distributionSha256Sum=abc10bcedb58806e8654210f96031db541bcd2d6fc3161e81cb0572d6a15e821 +#distributionSha256Sum=abc10bcedb58806e8654210f96031db541bcd2d6fc3161e81cb0572d6a15e821 diff --git a/lib/home/audioplayer.dart b/lib/home/audioplayer.dart index a000ddb..07b867d 100644 --- a/lib/home/audioplayer.dart +++ b/lib/home/audioplayer.dart @@ -303,8 +303,9 @@ class LastPosition extends StatelessWidget { .onSurface .withOpacity(0.12))), textColor: data ? Colors.white : null, - onPressed: () => - audio.setSkipSilence(skipSilence: !data))), + onPressed: () => {} + //audio.setSkipSilence(skipSilence: !data) + )), SizedBox(width: 10), Selector( selector: (_, audio) => audio.boostVolume, @@ -328,8 +329,9 @@ class LastPosition extends StatelessWidget { .onSurface .withOpacity(0.12))), textColor: data ? Colors.white : null, - onPressed: () => - audio.setBoostVolume(boostVolume: !data))), + onPressed: () => {} + // audio.setBoostVolume(boostVolume: !data) + )), SizedBox(width: 10), FutureBuilder( future: getPosition(episode), @@ -1336,13 +1338,7 @@ class _ControlPanelState extends State AudioProcessingState .buffering || data.audioState == - AudioProcessingState - .connecting || - data.audioState == - AudioProcessingState.none || - data.audioState == - AudioProcessingState - .skippingToNext + AudioProcessingState.loading ? context.s.buffering : '', style: TextStyle( diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index 021a789..9e6d845 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:math' as math; +import 'dart:developer'; import 'package:audio_service/audio_service.dart'; import 'package:audio_session/audio_session.dart'; @@ -179,18 +179,19 @@ class AudioPlayerNotifier extends ChangeNotifier { @override void addListener(VoidCallback listener) async { - super.addListener(listener); - _initAudioData(); - _audioHandler = await AudioService.init( + await _initAudioData(); + final _audioHandler = await AudioService.init( builder: () => CustomAudioHandler(), config: AudioServiceConfig( androidNotificationChannelName: 'Tsacdop', androidNotificationIcon: 'drawable/ic_notification', androidEnableQueue: true, androidStopForegroundOnPause: true, + preloadArtwork: false, fastForwardInterval: Duration(seconds: _fastForwardSeconds), rewindInterval: Duration(seconds: _rewindSeconds)), ); + super.addListener(listener); } /// Audio playing state. @@ -249,6 +250,9 @@ class AudioPlayerNotifier extends ChangeNotifier { _skipSilence = await _skipSilenceStorage.getBool(defaultValue: false); _boostVolume = await _boostVolumeStorage.getBool(defaultValue: false); _volumeGain = await _volumeGainStorage.getInt(defaultValue: 3000); + _fastForwardSeconds = + await _fastForwardSecondsStorage.getInt(defaultValue: 30); + _rewindSeconds = await _rewindSecondsStorage.getInt(defaultValue: 30); } Future _savePlayerHeight() async { @@ -446,11 +450,11 @@ class AudioPlayerNotifier extends ChangeNotifier { //Check autoplay setting, if true only add one episode, else add playlist. await _getAutoPlay(); if (_autoPlay) { - for (var episode in playlist.episodes) { - await _audioHandler.addQueueItem(episode.toMediaItem()); - } + await _audioHandler.addQueueItems( + ([for (var episode in playlist.episodes) episode.toMediaItem()])); } else { - await _audioHandler.addQueueItem(playlist.episodes[index].toMediaItem()); + await _audioHandler + .addQueueItems([playlist.episodes[index].toMediaItem()]); } //Check auto sleep timer setting await _getAutoSleepTimer(); @@ -487,40 +491,40 @@ class AudioPlayerNotifier extends ChangeNotifier { // 'setBoostVolume', [_boostVolume, _volumeGain]); // } - await _audioHandler.play(); - _audioHandler.mediaItem - .where((event) => event != null) - .listen((item) async { - var episode = await _dbHelper.getRssItemWithMediaId(item.id); - if (episode == null) { - episode = _playFromSearchList.firstWhere((e) => e.mediaId == item.id, - orElse: () => null); - } - _backgroundAudioDuration = item.duration?.inMilliseconds ?? 0; - if (episode != null) { - _episode = episode; - _backgroundAudioDuration = item.duration.inMilliseconds ?? 0; - if (position > 0 && - _backgroundAudioDuration > 0 && - _episode.enclosureUrl == _playlist.episodeList[index]) { - await AudioService.seekTo(Duration(milliseconds: position)); - position = 0; + _audioHandler.play(); + + _audioHandler.mediaItem.where((event) => event != null).listen( + (item) async { + var episode = await _dbHelper.getRssItemWithMediaId(item.id); + if (episode == null) { + episode = _playFromSearchList.firstWhere((e) => e.mediaId == item.id, + orElse: () => null); } - notifyListeners(); - } else { - AudioService.skipToNext(); - } - }); + if (episode != null) { + _episode = episode; + _backgroundAudioDuration = item.duration.inMilliseconds ?? 0; + if (position > 0 && + _backgroundAudioDuration > 0 && + _episode.enclosureUrl == _playlist.episodeList[index]) { + await _audioHandler.seek(Duration(milliseconds: position)); + position = 0; + } + notifyListeners(); + } else { + _audioHandler.skipToNext(); + } + }, + ); + _audioHandler.playbackState - .distinct() .where((event) => event != null) .listen((event) async { + print(event.toString()); _current = DateTime.now(); _audioState = event.processingState; _playing = event?.playing; _currentSpeed = event.speed; - _currentPosition = event.position.inMilliseconds ?? 0; - + _currentPosition = event.updatePosition.inMilliseconds ?? 0; if (_audioState == AudioProcessingState.completed) { if (_switchValue > 0) _switchValue = 0; } @@ -570,23 +574,12 @@ class AudioPlayerNotifier extends ChangeNotifier { } //_episode = null; } - }); - - //double s = _currentSpeed ?? 1.0; - var getPosition = 0; - Timer.periodic(Duration(milliseconds: 500), (timer) { - var s = _currentSpeed ?? 1.0; - if (_noSlide) { - if (_playing && !buffering) { - getPosition = _currentPosition + - ((DateTime.now().difference(_current).inMilliseconds) * s) - .toInt(); - _backgroundAudioPosition = - math.min(getPosition, _backgroundAudioDuration); - } else { - _backgroundAudioPosition = _currentPosition ?? 0; - } - + if (event is Map && event['duration'] != null) { + _backgroundAudioDuration = event['duration'].inMilliseconds; + notifyListeners(); + } + if (event is Map && event['position'] != null) { + _backgroundAudioPosition = event['position'].inMilliseconds; if (_backgroundAudioDuration != null && _backgroundAudioDuration != 0 && _backgroundAudioPosition != null) { @@ -595,19 +588,46 @@ class AudioPlayerNotifier extends ChangeNotifier { } else { _seekSliderValue = 0; } - - if (_backgroundAudioPosition > 0 && - _backgroundAudioPosition < _backgroundAudioDuration) { - _lastPosition = _backgroundAudioPosition; - _playerStateStorage.savePlayerState( - _playlist.id, _episode.enclosureUrl, _lastPosition); - } notifyListeners(); } - if (_audioState == AudioProcessingState.completed) { - timer.cancel(); - } }); + + //double s = _currentSpeed ?? 1.0; + // var getPosition = 0; + // Timer.periodic(Duration(milliseconds: 500), (timer) { + // var s = _currentSpeed ?? 1.0; + // if (_noSlide) { + // if (_playing && !buffering) { + // getPosition = _currentPosition + + // ((DateTime.now().difference(_current).inMilliseconds) * s) + // .toInt(); + // _backgroundAudioPosition = + // math.min(getPosition, _backgroundAudioDuration); + // } else { + // _backgroundAudioPosition = _currentPosition ?? 0; + // } + + // if (_backgroundAudioDuration != null && + // _backgroundAudioDuration != 0 && + // _backgroundAudioPosition != null) { + // _seekSliderValue = + // _backgroundAudioPosition / _backgroundAudioDuration ?? 0; + // } else { + // _seekSliderValue = 0; + // } + + // if (_backgroundAudioPosition > 0 && + // _backgroundAudioPosition < _backgroundAudioDuration) { + // _lastPosition = _backgroundAudioPosition; + // _playerStateStorage.savePlayerState( + // _playlist.id, _episode.enclosureUrl, _lastPosition); + // } + // notifyListeners(); + // } + // if (_audioState == AudioProcessingState.completed) { + // timer.cancel(); + // } + // }); } /// Queue management. @@ -972,32 +992,36 @@ class CustomAudioHandler extends BaseAudioHandler final cacheStorage = KeyValueStorage(cacheMaxKey); final layoutStorage = KeyValueStorage(notificationLayoutKey); final AudioPlayer _player = AudioPlayer(); - AudioSession _session; - bool _playing; bool _interrupted = false; + int _layoutIndex; bool _stopAtEnd; int _cacheMax; - bool _isQueue; - int _index = 0; + bool _isQueue = false; bool get hasNext => queue.value.length > 0; MediaItem get currentMediaItem => mediaItem.value; bool get playing => playbackState.value.playing; // MediaItem get _currentMediaItem => hasNext ? _queue[_index] : null; - BehaviorSubject> customEvent; + BehaviorSubject> customEvent = + BehaviorSubject.seeded({}); CustomAudioHandler() { - _player.currentIndexStream - .listen((index) => mediaItem.add(queue.value[index])); - _player.playbackEventStream.listen((event) { + _handleInterruption(); + _player.currentIndexStream.listen( + (index) { + if (queue.value.isNotEmpty) { + mediaItem.add(queue.value[index]); + } + }, + ); + _player.playbackEventStream.listen((event) async { + if (_layoutIndex == null) { + _layoutIndex = await layoutStorage.getInt(); + } playbackState.add(playbackState.value.copyWith( - controls: [ - MediaControl.skipToPrevious, - playing ? MediaControl.pause : MediaControl.play, - MediaControl.skipToNext, - ], - androidCompactActionIndices: [0, 1, 3], + controls: _getControls(_layoutIndex), + androidCompactActionIndices: [0, 1, 2], systemActions: { MediaAction.seek, MediaAction.seekForward, @@ -1016,33 +1040,51 @@ class CustomAudioHandler extends BaseAudioHandler speed: _player.speed, )); }); - _configureSession(); + + _player.positionStream.listen((event) { + customEvent.add({'position': event}); + }); + + _player.durationStream.listen((event) { + log(event.toString()); + mediaItem.add(mediaItem.value.copyWith(duration: _player.duration)); + customEvent.add({'duration': event}); + }); } - Future _configureSession() async { - _session = await AudioSession.instance; - await _session.configure(AudioSessionConfiguration.speech()); - _handleInterruption(_session); + Future addQueueItems(List items) async { + super.addQueueItems(items); + await _player.setAudioSource( + ConcatenatingAudioSource( + useLazyPreparation: true, + shuffleOrder: DefaultShuffleOrder(), + children: [ + for (var item in items) AudioSource.uri(Uri.parse(item.id)), + ], + ), + ); } - void _handleInterruption(AudioSession session) async { + void _handleInterruption() async { + final session = await AudioSession.instance; + await session.configure(AudioSessionConfiguration.speech()); session.interruptionEventStream.listen((event) { if (event.begin) { switch (event.type) { case AudioInterruptionType.pause: - if (_playing) { + if (playing) { pause(); _interrupted = true; } break; case AudioInterruptionType.duck: - if (_playing) { + if (playing) { pause(); _interrupted = true; } break; case AudioInterruptionType.unknown: - if (_playing) { + if (playing) { pause(); _interrupted = true; } @@ -1051,12 +1093,12 @@ class CustomAudioHandler extends BaseAudioHandler } else { switch (event.type) { case AudioInterruptionType.pause: - if (!_playing && _interrupted) { + if (!playing && _interrupted) { play(); } break; case AudioInterruptionType.duck: - if (!_playing && _interrupted) { + if (!playing && _interrupted) { play(); } break; @@ -1067,19 +1109,19 @@ class CustomAudioHandler extends BaseAudioHandler } }); session.becomingNoisyEventStream.listen((_) { - if (_playing) pause(); + if (playing) pause(); }); } - void _handlePlaybackCompleted() async { - if (hasNext) { - skipToNext(); - } else { - _player.stop(); - removeQueueItemAt(0); - stop(); - } - } + // void _handlePlaybackCompleted() async { + // if (hasNext) { + // skipToNext(); + // } else { + // _player.stop(); + // removeQueueItemAt(0); + // stop(); + // } + // } void playPause() { if (playbackState.value.playing) { @@ -1090,24 +1132,29 @@ class CustomAudioHandler extends BaseAudioHandler } Future skipToNext() async { - customEvent.add({'mediaItem': currentMediaItem.title}); - if (_isQueue) { - if (queue.value.length > 0) { - removeQueueItemAt(0); - } + if (_isQueue && queue.value.length > 0) { + removeQueueItemAt(0); } if (queue.value.length == 0 || _stopAtEnd) { await Future.delayed(Duration(milliseconds: 200)); await stop(); } else { - super.skipToNext(); - var duration = await _player.durationFuture; - if (duration != null) { - mediaItem.add(currentMediaItem.copyWith(duration: duration)); - } + await super.skipToNext(); + // _updateDuration(); } } + // _updateDuration() async { + // var duration = await _player.durationFuture; + // if (duration != null) { + // mediaItem.add(currentMediaItem.copyWith(duration: duration)); + // } + // } + + _setMediaItem(MediaItem item) { + mediaItem.add(item); + } + Future play() async { if (playing == null) { // _cacheMax = await cacheStorage.getInt( @@ -1116,41 +1163,42 @@ class CustomAudioHandler extends BaseAudioHandler // await cacheStorage.saveInt((200 * 1024 * 1024).toInt()); // _cacheMax = 200 * 1024 * 1024; // } - await _player.setUrl(queue.value[_index].id); - var duration = await _player.durationFuture; - if (duration != null) { - mediaItem.add(currentMediaItem.copyWith(duration: duration)); - } + await super.play(); + _player.play(); _playFromStart(); + // _updateDuration(); } else { - _session.setActive(true); + // _session.setActive(true); + super.play(); await _player.play(); await _seekRelative(Duration(seconds: -3)); } } Future _playFromStart() async { - _playing = true; - _session.setActive(true); + // _session.setActive(true); if (currentMediaItem.extras['skipSecondsStart'] > 0 || currentMediaItem.extras['skipSecondsEnd'] > 0) { _player .seek(Duration(seconds: mediaItem.value.extras['skipSecondsStart'])); } - try { - _player.play(); - } catch (e) { - playbackState.add(playbackState.value - .copyWith(processingState: AudioProcessingState.error)); - } } Future pause() async { - _player.pause(); + await _player.pause(); } - Future seekTo(Duration position) async { + Future seek(Duration position) async { await _player.seek(position); + super.seek(position); + } + + Future fastForward() async { + _seekRelative(AudioService.config.fastForwardInterval); + } + + Future rewind() async { + _seekRelative(-AudioService.config.rewindInterval); } Future onClick(MediaButton button) async { @@ -1174,7 +1222,7 @@ class CustomAudioHandler extends BaseAudioHandler Future _seekRelative(Duration offset) async { var newPosition = playbackState.value.position + offset; if (newPosition < Duration.zero) newPosition = Duration.zero; - seekTo(newPosition); + seek(newPosition); } Future stop() async { @@ -1198,17 +1246,14 @@ class CustomAudioHandler extends BaseAudioHandler queue.add(queue.value..insert(index, item)); // await _player.setUrl(mediaItem.id, cacheMax: _cacheMax); await _player.setUrl(item.id); - mediaItem.add(item); - var duration = await _player.durationFuture ?? Duration.zero; - mediaItem.add(mediaItem.value.copyWith(duration: duration)); + super.play(); + // _updateDuration(); _playFromStart(); - //onPlay(); } else { queue.add(queue.value..insert(index, item)); } } - @override Future customAction(funtion, argument) async { switch (funtion) { case 'stopAtEnd': @@ -1242,24 +1287,16 @@ class CustomAudioHandler extends BaseAudioHandler Future _changeQueue(List newQueue) async { await _player.stop(); - customEvent.add({'mediaItem': currentMediaItem.title}); queue.add(newQueue); - _index = 0; - await _player.setUrl(currentMediaItem.id); - var duration = await _player.durationFuture ?? Duration.zero; - mediaItem.add(currentMediaItem.copyWith(duration: duration)); + await super.play(); _playFromStart(); + // _updateDuration(); } Future _changeIndex(int index) async { - await _player.stop(); - customEvent.add({'mediaItem': currentMediaItem.title}); - _index = index; - mediaItem.add(currentMediaItem); - await _player.setUrl(currentMediaItem.id); - var duration = await _player.durationFuture ?? Duration.zero; - mediaItem.add(currentMediaItem.copyWith(duration: duration)); + await super.skipToQueueItem(index); _playFromStart(); + // _updateDuration(); } // Future _setSkipSilence(bool boo) async { @@ -1301,7 +1338,7 @@ class CustomAudioHandler extends BaseAudioHandler switch (index) { case 0: return [ - _playing ? pauseControl : playControl, + playing ? pauseControl : playControl, forwardControl, skipToNextControl, stopControl @@ -1309,7 +1346,7 @@ class CustomAudioHandler extends BaseAudioHandler break; case 1: return [ - _playing ? pauseControl : playControl, + playing ? pauseControl : playControl, rewindControl, skipToNextControl, stopControl @@ -1318,7 +1355,7 @@ class CustomAudioHandler extends BaseAudioHandler case 2: return [ rewindControl, - _playing ? pauseControl : playControl, + playing ? pauseControl : playControl, forwardControl, stopControl ]; @@ -1326,7 +1363,7 @@ class CustomAudioHandler extends BaseAudioHandler break; default: return [ - _playing ? pauseControl : playControl, + playing ? pauseControl : playControl, forwardControl, skipToNextControl, stopControl diff --git a/pubspec.yaml b/pubspec.yaml index 6208f27..21668b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,43 +13,42 @@ dependencies: sdk: flutter auto_animated: ^3.0.0 audio_session: ^0.1.0 - cached_network_image: ^2.5.1 + cached_network_image: ^3.0.0 color_thief_flutter: ^1.0.2 confetti: ^0.5.5 cupertino_icons: ^1.0.2 - connectivity: ^3.0.2 - crypto: ^3.0.0 + connectivity: ^3.0.3 + crypto: ^3.0.1 device_info: ^2.0.0 - dio: ^3.0.10 dio_cookie_manager: ^1.0.0 + dio: ^3.0.10 extended_nested_scroll_view: ^3.0.0 effective_dart: ^1.3.1 - equatable: ^1.2.5 - feature_discovery: ^0.13.0+2 - file_picker: ^2.1.4 + feature_discovery: ^0.14.0 + file_picker: ^3.0.1 flutter_html: ^0.11.1 fluttertoast: ^4.0.1 - flutter_isolate: ^1.0.0+14 - flutter_linkify: ^4.0.2 - flutter_file_dialog: ^1.0.0 - flare_flutter: ^2.0.6 + flutter_isolate: ^2.0.0 + flutter_linkify: ^4.1.0 + flutter_file_dialog: ^2.0.0 + flare_flutter: ^3.0.0 fl_chart: ^0.12.2 - line_icons: ^1.3.2 - flutter_media_metadata: ^0.0.3+2 - marquee: ^1.7.0 + line_icons: ^2.0.1 + flutter_media_metadata: ^0.1.0 + marquee: ^2.0.0 google_fonts: ^2.0.0 - image: ^3.0.1 + image: ^3.0.2 intl: ^0.17.0 - json_serializable: ^3.5.1 - json_annotation: ^3.1.1 - permission_handler: ^5.0.1 - provider: ^4.3.2 - sqflite: ^2.0.0+2 - state_notifier: ^0.6.0 - tuple: ^1.0.3 - url_launcher: ^5.7.10 + json_serializable: ^4.1.0 + json_annotation: ^4.0.1 + permission_handler: ^6.1.1 + provider: ^5.0.0 + sqflite: ^2.0.0+3 + state_notifier: ^0.7.0 + tuple: ^2.0.0 + url_launcher: ^6.0.3 workmanager: ^0.2.3 - wc_flutter_share: ^0.2.2 + wc_flutter_share: ^0.4.0 just_audio: ^0.7.3 audio_service: git: @@ -67,6 +66,7 @@ dependencies: url: https://github.com/stonega/webfeed.git dependency_overrides: + equatable: ^2.0.0 path_provider: ^2.0.1 http_parser: ^4.0.0 cookie_jar: ^2.0.0 @@ -75,7 +75,7 @@ dependency_overrides: flutter_cache_manager: ^3.0.1 rxdart: ^0.26.0 shared_preferences: 2.0.0 - plugin_platform_interface: ^1.0.1 + plugin_platform_interface: ^2.0.0 convert: ^3.0.0 xml: ^5.0.2 linkify: From 9ff8f84892c5920db2b99897e9ce69bc371c4643 Mon Sep 17 00:00:00 2001 From: stonega Date: Sun, 2 May 2021 00:02:12 +0800 Subject: [PATCH 3/7] Add new audio plugin. --- android/app/src/main/AndroidManifest.xml | 2 +- lib/home/audioplayer.dart | 10 +- lib/state/audio_state.dart | 244 ++++++++--------------- pubspec.yaml | 6 +- 4 files changed, 88 insertions(+), 174 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bfa7fe2..92d03ae 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -8,7 +8,7 @@ - + diff --git a/lib/home/audioplayer.dart b/lib/home/audioplayer.dart index 07b867d..8f57041 100644 --- a/lib/home/audioplayer.dart +++ b/lib/home/audioplayer.dart @@ -303,9 +303,8 @@ class LastPosition extends StatelessWidget { .onSurface .withOpacity(0.12))), textColor: data ? Colors.white : null, - onPressed: () => {} - //audio.setSkipSilence(skipSilence: !data) - )), + onPressed: () => + audio.setSkipSilence(skipSilence: !data))), SizedBox(width: 10), Selector( selector: (_, audio) => audio.boostVolume, @@ -329,9 +328,8 @@ class LastPosition extends StatelessWidget { .onSurface .withOpacity(0.12))), textColor: data ? Colors.white : null, - onPressed: () => {} - // audio.setBoostVolume(boostVolume: !data) - )), + onPressed: () => + audio.setBoostVolume(boostVolume: !data))), SizedBox(width: 10), FutureBuilder( future: getPosition(episode), diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index 9e6d845..5d98695 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -75,6 +75,7 @@ class AudioPlayerNotifier extends ChangeNotifier { KeyValueStorage(markListenedAfterSkipKey); final _playlistsStorgae = KeyValueStorage(playlistsAllKey); final _playerStateStorage = KeyValueStorage(playerStateKey); + final cacheStorage = KeyValueStorage(cacheMaxKey); /// Playing episdoe. EpisodeBrief _episode; @@ -175,13 +176,18 @@ class AudioPlayerNotifier extends ChangeNotifier { List _playFromSearchList = []; AudioHandler _audioHandler; - AudioSession _session; + + StreamSubscription _mediaitemSubscription; + StreamSubscription _playbackStateSubscription; + StreamSubscription _customEventSubscription; @override void addListener(VoidCallback listener) async { await _initAudioData(); - final _audioHandler = await AudioService.init( - builder: () => CustomAudioHandler(), + final cacheMax = + await cacheStorage.getInt(defaultValue: (1024 * 1024 * 200).toInt()); + _audioHandler = await AudioService.init( + builder: () => CustomAudioHandler(cacheMax), config: AudioServiceConfig( androidNotificationChannelName: 'Tsacdop', androidNotificationIcon: 'drawable/ic_notification', @@ -194,6 +200,14 @@ class AudioPlayerNotifier extends ChangeNotifier { super.addListener(listener); } + @override + void dispose() { + _mediaitemSubscription?.cancel(); + _playbackStateSubscription?.cancel(); + _customEventSubscription?.cancel(); + super.dispose(); + } + /// Audio playing state. AudioProcessingState get audioState => _audioState; int get backgroundAudioDuration => _backgroundAudioDuration; @@ -482,18 +496,20 @@ class AudioPlayerNotifier extends ChangeNotifier { /// Set slipsilence. if (_skipSilence) { - // await _audioHandler.customAction('setSkipSilence', {'': skipSilence}); + await _audioHandler + .customAction('setSkipSilence', {'skipSilence': skipSilence}); } /// Set boostValome. - // if (_boostVolume) { - // await _audioHandler.customAction( - // 'setBoostVolume', [_boostVolume, _volumeGain]); - // } + if (_boostVolume) { + await _audioHandler.customAction( + 'setBoostVolume', {'boostVolume': _boostVolume, 'gain': _volumeGain}); + } _audioHandler.play(); - _audioHandler.mediaItem.where((event) => event != null).listen( + _mediaitemSubscription = + _audioHandler.mediaItem.where((event) => event != null).listen( (item) async { var episode = await _dbHelper.getRssItemWithMediaId(item.id); if (episode == null) { @@ -502,7 +518,7 @@ class AudioPlayerNotifier extends ChangeNotifier { } if (episode != null) { _episode = episode; - _backgroundAudioDuration = item.duration.inMilliseconds ?? 0; + _backgroundAudioDuration = item.duration?.inMilliseconds ?? 0; if (position > 0 && _backgroundAudioDuration > 0 && _episode.enclosureUrl == _playlist.episodeList[index]) { @@ -516,10 +532,9 @@ class AudioPlayerNotifier extends ChangeNotifier { }, ); - _audioHandler.playbackState + _playbackStateSubscription = _audioHandler.playbackState .where((event) => event != null) .listen((event) async { - print(event.toString()); _current = DateTime.now(); _audioState = event.processingState; _playing = event?.playing; @@ -541,11 +556,12 @@ class AudioPlayerNotifier extends ChangeNotifier { notifyListeners(); }); - _audioHandler.customEvent.distinct().listen((event) async { - if (event is String) { + _customEventSubscription = + _audioHandler.customEvent.distinct().listen((event) async { + if (event is Map && event['removePlayed'] != null) { if (_playlist.isQueue && _queue.isNotEmpty && - _queue.episodes.first.title == event) { + _queue.episodes.first.title == event['removePlayed']) { _queue.delFromPlaylist(_episode); updatePlaylist(_queue, updateEpisodes: false); } @@ -574,10 +590,6 @@ class AudioPlayerNotifier extends ChangeNotifier { } //_episode = null; } - if (event is Map && event['duration'] != null) { - _backgroundAudioDuration = event['duration'].inMilliseconds; - notifyListeners(); - } if (event is Map && event['position'] != null) { _backgroundAudioPosition = event['position'].inMilliseconds; if (_backgroundAudioDuration != null && @@ -591,43 +603,6 @@ class AudioPlayerNotifier extends ChangeNotifier { notifyListeners(); } }); - - //double s = _currentSpeed ?? 1.0; - // var getPosition = 0; - // Timer.periodic(Duration(milliseconds: 500), (timer) { - // var s = _currentSpeed ?? 1.0; - // if (_noSlide) { - // if (_playing && !buffering) { - // getPosition = _currentPosition + - // ((DateTime.now().difference(_current).inMilliseconds) * s) - // .toInt(); - // _backgroundAudioPosition = - // math.min(getPosition, _backgroundAudioDuration); - // } else { - // _backgroundAudioPosition = _currentPosition ?? 0; - // } - - // if (_backgroundAudioDuration != null && - // _backgroundAudioDuration != 0 && - // _backgroundAudioPosition != null) { - // _seekSliderValue = - // _backgroundAudioPosition / _backgroundAudioDuration ?? 0; - // } else { - // _seekSliderValue = 0; - // } - - // if (_backgroundAudioPosition > 0 && - // _backgroundAudioPosition < _backgroundAudioDuration) { - // _lastPosition = _backgroundAudioPosition; - // _playerStateStorage.savePlayerState( - // _playlist.id, _episode.enclosureUrl, _lastPosition); - // } - // notifyListeners(); - // } - // if (_audioState == AudioProcessingState.completed) { - // timer.cancel(); - // } - // }); } /// Queue management. @@ -904,30 +879,31 @@ class AudioPlayerNotifier extends ChangeNotifier { notifyListeners(); } - /// Set skip silence. - // Future setSkipSilence({@required bool skipSilence}) async { - // await AudioService.customAction('setSkipSilence', skipSilence); - // _skipSilence = skipSilence; - // await _skipSilenceStorage.saveBool(_skipSilence); - // notifyListeners(); - // } + // Set skip silence. + Future setSkipSilence({@required bool skipSilence}) async { + await _audioHandler + .customAction('setSkipSilence', {'skipSilence': skipSilence}); + _skipSilence = skipSilence; + await _skipSilenceStorage.saveBool(_skipSilence); + notifyListeners(); + } set setVolumeGain(int volumeGain) { _volumeGain = volumeGain; if (_playerRunning && _boostVolume) { - // setBoostVolume(boostVolume: _boostVolume, gain: _volumeGain); + setBoostVolume(boostVolume: _boostVolume, gain: _volumeGain); } notifyListeners(); _volumeGainStorage.saveInt(volumeGain); } - // Future setBoostVolume({@required bool boostVolume, int gain}) async { - // await _audioHandler.customAction( - // 'setBoostVolume', [boostVolume, _volumeGain]); - // _boostVolume = boostVolume; - // notifyListeners(); - // await _boostVolumeStorage.saveBool(boostVolume); - // } + Future setBoostVolume({@required bool boostVolume, int gain}) async { + await _audioHandler.customAction( + 'setBoostVolume', {'boostVolume': boostVolume, 'gain': _volumeGain}); + _boostVolume = boostVolume; + notifyListeners(); + await _boostVolumeStorage.saveBool(boostVolume); + } //Set sleep timer void sleepTimer(int mins) { @@ -994,25 +970,29 @@ class CustomAudioHandler extends BaseAudioHandler final AudioPlayer _player = AudioPlayer(); bool _interrupted = false; int _layoutIndex; - bool _stopAtEnd; - int _cacheMax; + bool _stopAtEnd = false; bool _isQueue = false; bool get hasNext => queue.value.length > 0; MediaItem get currentMediaItem => mediaItem.value; bool get playing => playbackState.value.playing; - // MediaItem get _currentMediaItem => hasNext ? _queue[_index] : null; BehaviorSubject> customEvent = BehaviorSubject.seeded({}); - CustomAudioHandler() { + CustomAudioHandler(int cacheMax) { + _player.cacheMax = cacheMax; _handleInterruption(); _player.currentIndexStream.listen( (index) { + log(index.toString()); if (queue.value.isNotEmpty) { mediaItem.add(queue.value[index]); } + if (_isQueue && index == 1) { + + customEvent.add({'removePlayed': queue.value.first.title}); + } }, ); _player.playbackEventStream.listen((event) async { @@ -1036,6 +1016,7 @@ class CustomAudioHandler extends BaseAudioHandler }[_player.processingState], playing: _player.playing, updatePosition: _player.position, + queueIndex: _player.currentIndex, bufferedPosition: _player.bufferedPosition, speed: _player.speed, )); @@ -1046,9 +1027,7 @@ class CustomAudioHandler extends BaseAudioHandler }); _player.durationStream.listen((event) { - log(event.toString()); mediaItem.add(mediaItem.value.copyWith(duration: _player.duration)); - customEvent.add({'duration': event}); }); } @@ -1059,7 +1038,11 @@ class CustomAudioHandler extends BaseAudioHandler useLazyPreparation: true, shuffleOrder: DefaultShuffleOrder(), children: [ - for (var item in items) AudioSource.uri(Uri.parse(item.id)), + for (var item in items) + ClippingAudioSource( + start: Duration(seconds: item.extras['skipSecondsStart']), + // end: Duration(seconds: item.extras['skipSecondsEnd']), + child: AudioSource.uri(Uri.parse(item.id))), ], ), ); @@ -1113,16 +1096,6 @@ class CustomAudioHandler extends BaseAudioHandler }); } - // void _handlePlaybackCompleted() async { - // if (hasNext) { - // skipToNext(); - // } else { - // _player.stop(); - // removeQueueItemAt(0); - // stop(); - // } - // } - void playPause() { if (playbackState.value.playing) { pause(); @@ -1132,56 +1105,32 @@ class CustomAudioHandler extends BaseAudioHandler } Future skipToNext() async { - if (_isQueue && queue.value.length > 0) { - removeQueueItemAt(0); - } if (queue.value.length == 0 || _stopAtEnd) { await Future.delayed(Duration(milliseconds: 200)); await stop(); } else { await super.skipToNext(); - // _updateDuration(); + _player.seekToNext(); + if (_isQueue && queue.value.isNotEmpty) { + removeQueueItemAt(0); + } } } - // _updateDuration() async { - // var duration = await _player.durationFuture; - // if (duration != null) { - // mediaItem.add(currentMediaItem.copyWith(duration: duration)); - // } - // } - - _setMediaItem(MediaItem item) { - mediaItem.add(item); - } - Future play() async { if (playing == null) { - // _cacheMax = await cacheStorage.getInt( - // defaultValue: (200 * 1024 * 1024).toInt()); - // if (_cacheMax == 0) { - // await cacheStorage.saveInt((200 * 1024 * 1024).toInt()); - // _cacheMax = 200 * 1024 * 1024; - // } await super.play(); _player.play(); - _playFromStart(); - // _updateDuration(); } else { - // _session.setActive(true); super.play(); await _player.play(); await _seekRelative(Duration(seconds: -3)); } } - Future _playFromStart() async { - // _session.setActive(true); - if (currentMediaItem.extras['skipSecondsStart'] > 0 || - currentMediaItem.extras['skipSecondsEnd'] > 0) { - _player - .seek(Duration(seconds: mediaItem.value.extras['skipSecondsStart'])); - } + Future removeQueueItemAt(int index) async { + queue.add(queue.value..removeAt(index)); + super.removeQueueItemAt(index); } Future pause() async { @@ -1240,15 +1189,9 @@ class CustomAudioHandler extends BaseAudioHandler Future _addQueueItemAt(MediaItem item, int index) async { if (index == 0 && _isQueue) { - await _player.stop(); - queue.add(queue.value..removeAt(0)); queue.add(queue.value..removeWhere((i) => i.id == item.id)); queue.add(queue.value..insert(index, item)); - // await _player.setUrl(mediaItem.id, cacheMax: _cacheMax); - await _player.setUrl(item.id); - super.play(); - // _updateDuration(); - _playFromStart(); + skipToNext(); } else { queue.add(queue.value..insert(index, item)); } @@ -1266,10 +1209,10 @@ class CustomAudioHandler extends BaseAudioHandler await _player.setSpeed(argument['speed']); break; case 'setSkipSilence': - // await _skipSilence(argument); + await _setSkipSilence(argument['skipSilence']); break; case 'setBoostVolume': - // await _setBoostVolume(argument[0], argument[1]); + await _setBoostVolume(argument['boostVolume'], argument['gain']); break; case 'setIsQueue': _isQueue = argument['isQueue']; @@ -1288,51 +1231,20 @@ class CustomAudioHandler extends BaseAudioHandler Future _changeQueue(List newQueue) async { await _player.stop(); queue.add(newQueue); - await super.play(); - _playFromStart(); - // _updateDuration(); + play(); } Future _changeIndex(int index) async { await super.skipToQueueItem(index); - _playFromStart(); - // _updateDuration(); } - // Future _setSkipSilence(bool boo) async { - // await _audioPlayer.setSkipSilence(boo); - // var duration = await _audioPlayer.durationFuture ?? Duration.zero; - // AudioServiceBackground.setMediaItem(mediaItem.copyWith(duration: duration)); - // } + Future _setSkipSilence(bool boo) async { + await _player.setSkipSilence(boo); + } - // Future _setBoostVolume(bool boo, int gain) async { - // await _audioPlayer.setBoostVolume(boo, gain: gain); - // } - - // Future _setState({ - // AudioProcessingState processingState, - // Duration position, - // Duration bufferedPosition, - // }) async { - // if (position == null) { - // position = _player.playbackEvent.position; - // } - // final index = await layoutStorage.getInt(defaultValue: 0); - // await playbackState.add( - // controls: _getControls(index), - // systemActions: [ - // MediaAction.seekTo, - // MediaAction.seekForward, - // MediaAction.seekBackward, - // ], - // processingState: - // processingState ?? AudioServiceBackground.state.processingState, - // playing: _playing ?? false, - // position: position, - // bufferedPosition: bufferedPosition ?? position, - // speed: _audioPlayer.speed, - // ); - // } + Future _setBoostVolume(bool boo, int gain) async { + await _player.setBoostVolume(boo, gain); + } List _getControls(int index) { switch (index) { diff --git a/pubspec.yaml b/pubspec.yaml index 21668b6..06918e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,7 +49,11 @@ dependencies: url_launcher: ^6.0.3 workmanager: ^0.2.3 wc_flutter_share: ^0.4.0 - just_audio: ^0.7.3 + just_audio: + git: + url: https://github.com/stonega/just_audio_origin.git + ref: update + path: just_audio audio_service: git: url: https://github.com/ryanheise/audio_service.git From f14f0f819c0514a341e9833b83f3444a4f9b1c11 Mon Sep 17 00:00:00 2001 From: stonega Date: Sat, 21 Aug 2021 17:25:52 +0800 Subject: [PATCH 4/7] Bump deps and improve error hande --- android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- lib/home/pocast_discovery.dart | 22 ++++++----- lib/home/search_podcast.dart | 3 ++ lib/playlists/playlist_home.dart | 8 ++-- lib/service/gpodder_api.dart | 9 +++-- lib/settings/history.dart | 4 +- lib/state/audio_state.dart | 6 +-- lib/util/cache_manager.dart | 27 ++++++++++++++ pubspec.yaml | 37 ++++++++----------- 10 files changed, 74 insertions(+), 46 deletions(-) create mode 100644 lib/util/cache_manager.dart diff --git a/android/build.gradle b/android/build.gradle index fdde64e..94e89a3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.6.2' + classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index fd9dc4d..772ece3 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip #distributionSha256Sum=abc10bcedb58806e8654210f96031db541bcd2d6fc3161e81cb0572d6a15e821 diff --git a/lib/home/pocast_discovery.dart b/lib/home/pocast_discovery.dart index 28df023..13294a4 100644 --- a/lib/home/pocast_discovery.dart +++ b/lib/home/pocast_discovery.dart @@ -197,16 +197,20 @@ class DiscoveryPageState extends State { } Future> _getTopPodcasts({int page}) async { - if(environment['apiKey'] == '') return []; + if (environment['apiKey'] == '') return []; final searchEngine = ListenNotesSearch(); - var searchResult = await searchEngine.fetchBestPodcast( - genre: '', - page: page, - ); - final podcastTopList = - searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList(); - _podcastList.addAll(podcastTopList.cast()); - return _podcastList; + try { + var searchResult = await searchEngine.fetchBestPodcast( + genre: '', + page: page, + ); + final podcastTopList = + searchResult.podcasts.map((e) => e?.toOnlinePodcast).toList(); + _podcastList.addAll(podcastTopList.cast()); + return _podcastList; + } catch (e) { + return []; + } } Future _getHideDiscovery() async { diff --git a/lib/home/search_podcast.dart b/lib/home/search_podcast.dart index 3eef2f1..8fb4ab6 100644 --- a/lib/home/search_podcast.dart +++ b/lib/home/search_podcast.dart @@ -11,6 +11,7 @@ import 'package:flutter_html/flutter_html.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; +import 'package:tsacdop/util/cache_manager.dart'; import 'package:webfeed/webfeed.dart'; import '../.env.dart'; @@ -251,6 +252,7 @@ class _RssResultState extends State { CachedNetworkImage( height: 120.0, width: 120.0, + cacheManager: CustomCacheManager(), fit: BoxFit.fitWidth, alignment: Alignment.center, imageUrl: _onlinePodcast.image, @@ -1356,6 +1358,7 @@ class PodcastAvatar extends StatelessWidget { fit: BoxFit.fitWidth, alignment: Alignment.center, imageUrl: podcast.image, + cacheManager: CustomCacheManager(), placeholderFadeInDuration: Duration.zero, progressIndicatorBuilder: (context, url, downloadProgress) => Container( diff --git a/lib/playlists/playlist_home.dart b/lib/playlists/playlist_home.dart index 0e04864..c7fb3e2 100644 --- a/lib/playlists/playlist_home.dart +++ b/lib/playlists/playlist_home.dart @@ -952,15 +952,14 @@ class __NewPlaylistState extends State<_NewPlaylist> { } Future _getEpisodeFromFile(String path) async { - var metadataRetriever = MetadataRetriever(); final fileLength = File(path).statSync().size; final pubDate = DateTime.now().millisecondsSinceEpoch; var primaryColor; var imagePath; - await metadataRetriever.setFile(File(path)); - if (metadataRetriever.albumArt != null) { + var metadata = await MetadataRetriever.fromFile(File(path)); + if (metadata.albumArt != null) { final dir = await getApplicationDocumentsDirectory(); - final image = img.decodeImage(metadataRetriever.albumArt); + final image = img.decodeImage(metadata.albumArt); final thumbnail = img.copyResize(image, width: 300); var uuid = Uuid().v4(); File("${dir.path}/$uuid.png")..writeAsBytesSync(img.encodePng(thumbnail)); @@ -968,7 +967,6 @@ class __NewPlaylistState extends State<_NewPlaylist> { primaryColor = await _getColor(File(imagePath)); } final fileName = path.split('/').last; - final metadata = await metadataRetriever.metadata; return EpisodeBrief( fileName, 'file://$path', diff --git a/lib/service/gpodder_api.dart b/lib/service/gpodder_api.dart index cdd780e..a360d53 100644 --- a/lib/service/gpodder_api.dart +++ b/lib/service/gpodder_api.dart @@ -31,13 +31,15 @@ class Gpodder { Future _initDio() async { final dir = await getApplicationDocumentsDirectory(); - var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/"); + var cookieJar = + PersistCookieJar(storage: FileStorage("${dir.path}/.cookies/")); _dio.interceptors.add(CookieManager(cookieJar)); } Future login({String username, String password}) async { final dir = await getApplicationDocumentsDirectory(); - var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/"); + var cookieJar = + PersistCookieJar(storage: FileStorage("${dir.path}/.cookies/")); cookieJar.delete(Uri.parse(_baseUrl)); _dio.interceptors.add(CookieManager(cookieJar)); final basicAuth = @@ -81,7 +83,8 @@ class Gpodder { Future _initService() async { final dir = await getApplicationDocumentsDirectory(); - var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/"); + var cookieJar = + PersistCookieJar(storage: FileStorage("${dir.path}/.cookies/")); cookieJar.delete(Uri.parse(_baseUrl)); await _storage.clearList(); await _addStorage.clearList(); diff --git a/lib/settings/history.dart b/lib/settings/history.dart index 398e1cf..3955f44 100644 --- a/lib/settings/history.dart +++ b/lib/settings/history.dart @@ -441,7 +441,7 @@ class HistoryChart extends StatelessWidget { titlesData: FlTitlesData( show: true, bottomTitles: SideTitles( - getTextStyles: (i) => TextStyle( + getTextStyles: (_, i) => TextStyle( color: const Color(0xff67727d), fontWeight: FontWeight.bold, fontSize: 12, @@ -456,7 +456,7 @@ class HistoryChart extends StatelessWidget { ), leftTitles: SideTitles( showTitles: true, - getTextStyles: (s) => TextStyle( + getTextStyles: (_, s) => TextStyle( color: const Color(0xff67727d), fontWeight: FontWeight.bold, fontSize: 12, diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index 5d98695..3c22a1b 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -977,8 +977,7 @@ class CustomAudioHandler extends BaseAudioHandler MediaItem get currentMediaItem => mediaItem.value; bool get playing => playbackState.value.playing; - BehaviorSubject> customEvent = - BehaviorSubject.seeded({}); + PublishSubject> customEvent = PublishSubject()..add({}); CustomAudioHandler(int cacheMax) { _player.cacheMax = cacheMax; @@ -990,7 +989,6 @@ class CustomAudioHandler extends BaseAudioHandler mediaItem.add(queue.value[index]); } if (_isQueue && index == 1) { - customEvent.add({'removePlayed': queue.value.first.title}); } }, @@ -1197,7 +1195,7 @@ class CustomAudioHandler extends BaseAudioHandler } } - Future customAction(funtion, argument) async { + Future customAction(funtion, [argument]) async { switch (funtion) { case 'stopAtEnd': _stopAtEnd = true; diff --git a/lib/util/cache_manager.dart b/lib/util/cache_manager.dart new file mode 100644 index 0000000..a68a1f9 --- /dev/null +++ b/lib/util/cache_manager.dart @@ -0,0 +1,27 @@ +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; + +/// The DefaultCacheManager that can be easily used directly. The code of +/// this implementation can be used as inspiration for more complex cache +/// managers. + +class CustomCacheManager extends CacheManager with ImageCacheManager { + static const key = 'libCachedImageData'; + + static final CustomCacheManager _instance = CustomCacheManager._(); + factory CustomCacheManager() { + return _instance; + } + + @override + Future downloadFile(String url, + {String key, Map authHeaders, bool force = false}) async { + var file; + try { + file = await super + .downloadFile(url, key: key, authHeaders: authHeaders, force: force); + } catch (e) {} + return file; + } + + CustomCacheManager._() : super(Config(key)); +} diff --git a/pubspec.yaml b/pubspec.yaml index 06918e1..86a1fd6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,30 +13,30 @@ dependencies: sdk: flutter auto_animated: ^3.0.0 audio_session: ^0.1.0 - cached_network_image: ^3.0.0 + cached_network_image: ^3.1.0 color_thief_flutter: ^1.0.2 confetti: ^0.5.5 cupertino_icons: ^1.0.2 connectivity: ^3.0.3 crypto: ^3.0.1 - device_info: ^2.0.0 - dio_cookie_manager: ^1.0.0 - dio: ^3.0.10 + device_info: ^2.0.2 + dio_cookie_manager: ^2.0.0 + dio: ^4.0.0 extended_nested_scroll_view: ^3.0.0 effective_dart: ^1.3.1 feature_discovery: ^0.14.0 file_picker: ^3.0.1 - flutter_html: ^0.11.1 + flutter_html: 0.11.1 fluttertoast: ^4.0.1 flutter_isolate: ^2.0.0 flutter_linkify: ^4.1.0 - flutter_file_dialog: ^2.0.0 - flare_flutter: ^3.0.0 - fl_chart: ^0.12.2 + flutter_file_dialog: ^2.1.1 + flare_flutter: ^3.0.2 + fl_chart: ^0.36.4 line_icons: ^2.0.1 - flutter_media_metadata: ^0.1.0 + flutter_media_metadata: ^0.1.1 marquee: ^2.0.0 - google_fonts: ^2.0.0 + google_fonts: ^2.1.0 image: ^3.0.2 intl: ^0.17.0 json_serializable: ^4.1.0 @@ -47,15 +47,17 @@ dependencies: state_notifier: ^0.7.0 tuple: ^2.0.0 url_launcher: ^6.0.3 + uuid: ^3.0.4 workmanager: ^0.2.3 wc_flutter_share: ^0.4.0 + xml: ^5.2.0 just_audio: git: url: https://github.com/stonega/just_audio_origin.git ref: update path: just_audio audio_service: - git: + git: url: https://github.com/ryanheise/audio_service.git ref: one-isolate path: audio_service @@ -68,20 +70,13 @@ dependencies: webfeed: git: url: https://github.com/stonega/webfeed.git - -dependency_overrides: equatable: ^2.0.0 path_provider: ^2.0.1 http_parser: ^4.0.0 - cookie_jar: ^2.0.0 - uuid: ^3.0.1 - http: ^0.13.0 - flutter_cache_manager: ^3.0.1 - rxdart: ^0.26.0 + +dependency_overrides: + meta: 1.3.0 shared_preferences: 2.0.0 - plugin_platform_interface: ^2.0.0 - convert: ^3.0.0 - xml: ^5.0.2 linkify: git: url: https://github.com/stonega/linkify.git From 8a128dc6ad90164edb7bed0268b257c472b3fe54 Mon Sep 17 00:00:00 2001 From: stonega Date: Sun, 5 Sep 2021 14:57:02 +0800 Subject: [PATCH 5/7] Update just audio to latest version --- lib/home/audioplayer.dart | 22 ++---- lib/home/pocast_discovery.dart | 6 +- lib/state/audio_state.dart | 118 +++++++++++++++++++++++---------- lib/util/cache_manager.dart | 25 ++++++- 4 files changed, 120 insertions(+), 51 deletions(-) diff --git a/lib/home/audioplayer.dart b/lib/home/audioplayer.dart index 8f57041..5d22288 100644 --- a/lib/home/audioplayer.dart +++ b/lib/home/audioplayer.dart @@ -457,18 +457,10 @@ class _PlaylistWidgetState extends State { children: [ Padding( padding: EdgeInsets.all(10.0), - child: ClipRRect( - borderRadius: - BorderRadius.all(Radius.circular(15.0)), - child: SizedBox( - height: 30.0, - width: 30.0, - child: Image( - image: episodes[index].avatarImage) - // Image.file(File( - // "${episodes[index].imagePath}")) - ), - ), + child: CircleAvatar( + radius: 15, + backgroundImage: + episodes[index].avatarImage), ), Expanded( child: Align( @@ -522,9 +514,9 @@ class _PlaylistWidgetState extends State { borderRadius: BorderRadius.all(Radius.circular(15)), onTap: () { audio.playNext(); - miniPlaylistKey.currentState.removeItem( - 0, (context, animation) => Container()); - miniPlaylistKey.currentState.insertItem(0); + // miniPlaylistKey.currentState.removeItem( + // 0, (context, animation) => Container()); + // miniPlaylistKey.currentState.insertItem(0); }, child: SizedBox( height: 30, diff --git a/lib/home/pocast_discovery.dart b/lib/home/pocast_discovery.dart index 13294a4..c78ed40 100644 --- a/lib/home/pocast_discovery.dart +++ b/lib/home/pocast_discovery.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:line_icons/line_icons.dart'; import 'package:provider/provider.dart'; @@ -408,7 +410,9 @@ class __TopPodcastListState extends State<_TopPodcastList> { @override void initState() { _page = 1; - _searchFuture = _getTopPodcasts(genre: widget.genre, page: _page); + try { + _searchFuture = _getTopPodcasts(genre: widget.genre, page: _page); + } catch (e) {} super.initState(); } diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index 3c22a1b..b89e70f 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -187,16 +187,7 @@ class AudioPlayerNotifier extends ChangeNotifier { final cacheMax = await cacheStorage.getInt(defaultValue: (1024 * 1024 * 200).toInt()); _audioHandler = await AudioService.init( - builder: () => CustomAudioHandler(cacheMax), - config: AudioServiceConfig( - androidNotificationChannelName: 'Tsacdop', - androidNotificationIcon: 'drawable/ic_notification', - androidEnableQueue: true, - androidStopForegroundOnPause: true, - preloadArtwork: false, - fastForwardInterval: Duration(seconds: _fastForwardSeconds), - rewindInterval: Duration(seconds: _rewindSeconds)), - ); + builder: () => CustomAudioHandler(cacheMax), config: _config); super.addListener(listener); } @@ -208,6 +199,17 @@ class AudioPlayerNotifier extends ChangeNotifier { super.dispose(); } + /// Audio service config + AudioServiceConfig get _config => AudioServiceConfig( + androidNotificationChannelName: 'Tsacdop', + androidNotificationIcon: 'drawable/ic_notification', + androidEnableQueue: true, + androidStopForegroundOnPause: true, + preloadArtwork: false, + fastForwardInterval: Duration(seconds: _fastForwardSeconds), + rewindInterval: Duration(seconds: _rewindSeconds), + ); + /// Audio playing state. AudioProcessingState get audioState => _audioState; int get backgroundAudioDuration => _backgroundAudioDuration; @@ -367,8 +369,7 @@ class AudioPlayerNotifier extends ChangeNotifier { notifyListeners(); if (playlist.isNotEmpty) { if (playerRunning) { - _audioHandler - .customAction('setIsQueue', {'isQueue': playlist.name == 'Queue'}); + _audioHandler.customAction('setIsQueue', {'isQueue': playlist.isQueue}); _audioHandler.customAction('changeQueue', { 'queue': [for (var e in p.episodes) e.toMediaItem()] }); @@ -394,7 +395,7 @@ class AudioPlayerNotifier extends ChangeNotifier { } else { episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); } - //TODO load episode from last position when player running + // @TODO load episode from last position when player running if (playerRunning) { if (_playFromSearchList.contains(_episode)) { _queue.delFromPlaylist(_episode); @@ -405,7 +406,7 @@ class AudioPlayerNotifier extends ChangeNotifier { } _queue.addToPlayListAt(episodeNew, 0); await updatePlaylist(_queue, updateEpisodes: !fromSearch); - if (!_playlist.isQueue) { + if (_playlist.isQueue) { _audioHandler.customAction('setIsQueue', {'isQueue': true}); _audioHandler.customAction('changeQueue', { 'queue': [for (var e in _queue.episodes) e.toMediaItem()] @@ -470,6 +471,8 @@ class AudioPlayerNotifier extends ChangeNotifier { await _audioHandler .addQueueItems([playlist.episodes[index].toMediaItem()]); } + + await _audioHandler.play(); //Check auto sleep timer setting await _getAutoSleepTimer(); if (_autoSleepTimer) { @@ -489,6 +492,10 @@ class AudioPlayerNotifier extends ChangeNotifier { } } + /// Set if playlist is queue. + await _audioHandler + .customAction('setIsQueue', {'isQueue': playlist.isQueue}); + /// Set player speed. if (_currentSpeed != 1.0) { await _audioHandler.customAction('setSpeed', {'speed': _currentSpeed}); @@ -559,9 +566,12 @@ class AudioPlayerNotifier extends ChangeNotifier { _customEventSubscription = _audioHandler.customEvent.distinct().listen((event) async { if (event is Map && event['removePlayed'] != null) { + log(event.toString()); + log(_queue.episodes.first.title); if (_playlist.isQueue && _queue.isNotEmpty && _queue.episodes.first.title == event['removePlayed']) { + log(event['removePlayed']); _queue.delFromPlaylist(_episode); updatePlaylist(_queue, updateEpisodes: false); } @@ -605,7 +615,7 @@ class AudioPlayerNotifier extends ChangeNotifier { }); } - /// Queue management. + /// Queue management Future addToPlaylist(EpisodeBrief episode) async { var episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); if (episodeNew.isNew == 1) { @@ -837,6 +847,10 @@ class AudioPlayerNotifier extends ChangeNotifier { Future playNext() async { _remoteErrorMessage = null; + if (_playlist.isQueue && _queue.isNotEmpty) { + _queue.delFromPlaylist(_episode); + updatePlaylist(_queue, updateEpisodes: false); + } await _audioHandler.skipToNext(); notifyListeners(); } @@ -972,6 +986,13 @@ class CustomAudioHandler extends BaseAudioHandler int _layoutIndex; bool _stopAtEnd = false; bool _isQueue = false; + bool _autoSkip = true; + + ConcatenatingAudioSource _playlist = ConcatenatingAudioSource( + useLazyPreparation: true, + shuffleOrder: DefaultShuffleOrder(), + children: [], + ); bool get hasNext => queue.value.length > 0; MediaItem get currentMediaItem => mediaItem.value; @@ -984,13 +1005,13 @@ class CustomAudioHandler extends BaseAudioHandler _handleInterruption(); _player.currentIndexStream.listen( (index) { - log(index.toString()); - if (queue.value.isNotEmpty) { + if (queue.value.isNotEmpty && index < queue.value.length) { mediaItem.add(queue.value[index]); } - if (_isQueue && index == 1) { + if (_isQueue && _autoSkip) { customEvent.add({'removePlayed': queue.value.first.title}); } + _autoSkip = true; }, ); _player.playbackEventStream.listen((event) async { @@ -1024,25 +1045,26 @@ class CustomAudioHandler extends BaseAudioHandler customEvent.add({'position': event}); }); + _player.sequenceStream.listen((event) { + log(event.toString()); + }); + _player.durationStream.listen((event) { mediaItem.add(mediaItem.value.copyWith(duration: _player.duration)); }); } + @override Future addQueueItems(List items) async { - super.addQueueItems(items); - await _player.setAudioSource( - ConcatenatingAudioSource( - useLazyPreparation: true, - shuffleOrder: DefaultShuffleOrder(), - children: [ - for (var item in items) - ClippingAudioSource( - start: Duration(seconds: item.extras['skipSecondsStart']), - // end: Duration(seconds: item.extras['skipSecondsEnd']), - child: AudioSource.uri(Uri.parse(item.id))), - ], - ), + queue.add(items); + _setAudioSource(items); + _player.setAudioSource(_playlist); + } + + void _setAudioSource(List items) { + _playlist.insertAll( + 0, + [for (var item in items) _itemToSource(item)], ); } @@ -1102,11 +1124,13 @@ class CustomAudioHandler extends BaseAudioHandler } } + @override Future skipToNext() async { if (queue.value.length == 0 || _stopAtEnd) { await Future.delayed(Duration(milliseconds: 200)); await stop(); } else { + _autoSkip = false; await super.skipToNext(); _player.seekToNext(); if (_isQueue && queue.value.isNotEmpty) { @@ -1115,10 +1139,12 @@ class CustomAudioHandler extends BaseAudioHandler } } + @override Future play() async { if (playing == null) { + log('playing'); await super.play(); - _player.play(); + await _player.play(); } else { super.play(); await _player.play(); @@ -1126,15 +1152,24 @@ class CustomAudioHandler extends BaseAudioHandler } } + @override + Future addQueueItem(MediaItem item) async { + _addQueueItemAt(item, queue.value.length); + } + + @override Future removeQueueItemAt(int index) async { queue.add(queue.value..removeAt(index)); + _playlist.removeAt(index); super.removeQueueItemAt(index); } + @override Future pause() async { await _player.pause(); } + @override Future seek(Duration position) async { await _player.seek(position); super.seek(position); @@ -1186,6 +1221,7 @@ class CustomAudioHandler extends BaseAudioHandler } Future _addQueueItemAt(MediaItem item, int index) async { + log(index.toString() + ': ' + item.toString()); if (index == 0 && _isQueue) { queue.add(queue.value..removeWhere((i) => i.id == item.id)); queue.add(queue.value..insert(index, item)); @@ -1193,10 +1229,12 @@ class CustomAudioHandler extends BaseAudioHandler } else { queue.add(queue.value..insert(index, item)); } + _playlist.insert(index, _itemToSource(item)); } - Future customAction(funtion, [argument]) async { - switch (funtion) { + @override + Future customAction(function, [argument]) async { + switch (function) { case 'stopAtEnd': _stopAtEnd = true; break; @@ -1204,6 +1242,7 @@ class CustomAudioHandler extends BaseAudioHandler _stopAtEnd = false; break; case 'setSpeed': + log('Argument' + argument['speed'].toString()); await _player.setSpeed(argument['speed']); break; case 'setSkipSilence': @@ -1213,6 +1252,7 @@ class CustomAudioHandler extends BaseAudioHandler await _setBoostVolume(argument['boostVolume'], argument['gain']); break; case 'setIsQueue': + log('Argument' + argument['isQueue'].toString()); _isQueue = argument['isQueue']; break; case 'changeQueue': @@ -1223,6 +1263,9 @@ class CustomAudioHandler extends BaseAudioHandler break; case 'addQueueItemAt': await _addQueueItemAt(argument['mediaItem'], argument['index']); + break; + default: + super.customAction(function, argument); } } @@ -1281,4 +1324,11 @@ class CustomAudioHandler extends BaseAudioHandler break; } } + + static AudioSource _itemToSource(MediaItem item) { + return ClippingAudioSource( + start: Duration(seconds: item.extras['skipSecondsStart']), + // end: Duration(seconds: item.extras['skipSecondsEnd']), + child: AudioSource.uri(Uri.parse(item.id))); + } } diff --git a/lib/util/cache_manager.dart b/lib/util/cache_manager.dart index a68a1f9..07a5154 100644 --- a/lib/util/cache_manager.dart +++ b/lib/util/cache_manager.dart @@ -19,9 +19,32 @@ class CustomCacheManager extends CacheManager with ImageCacheManager { try { file = await super .downloadFile(url, key: key, authHeaders: authHeaders, force: force); - } catch (e) {} + } catch (e) { + rethrow; + } return file; } + @override + Stream getImageFile( + String url, { + String key, + Map headers, + bool withProgress = false, + int maxHeight, + int maxWidth, + }) async* { + try { + super.getImageFile(url, + key: key, + headers: headers, + withProgress: withProgress, + maxHeight: maxHeight, + maxWidth: maxWidth); + } catch (e) { + + } + } + CustomCacheManager._() : super(Config(key)); } From ad7541d2fc455e774d39c0e237ef13fd9a5f7a9a Mon Sep 17 00:00:00 2001 From: stonega Date: Wed, 6 Oct 2021 18:47:09 +0800 Subject: [PATCH 6/7] Update audio state controller --- android/app/build.gradle | 1 + lib/state/audio_state.dart | 21 +++++++++++++-------- pubspec.yaml | 6 +----- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index f4e182a..4fa572b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -42,6 +42,7 @@ android { lintOptions { disable 'InvalidPackage' + checkReleaseBuilds false } defaultConfig { diff --git a/lib/state/audio_state.dart b/lib/state/audio_state.dart index b89e70f..7ccffef 100644 --- a/lib/state/audio_state.dart +++ b/lib/state/audio_state.dart @@ -203,7 +203,7 @@ class AudioPlayerNotifier extends ChangeNotifier { AudioServiceConfig get _config => AudioServiceConfig( androidNotificationChannelName: 'Tsacdop', androidNotificationIcon: 'drawable/ic_notification', - androidEnableQueue: true, + // androidEnableQueue: true, androidStopForegroundOnPause: true, preloadArtwork: false, fastForwardInterval: Duration(seconds: _fastForwardSeconds), @@ -357,6 +357,8 @@ class AudioPlayerNotifier extends ChangeNotifier { _startAudioService(_playlist, position: _lastPosition ?? 0, index: _playlist.episodes.indexOf(_episode)); + } else { + log('Playlist is empty'); } } @@ -598,7 +600,11 @@ class AudioPlayerNotifier extends ChangeNotifier { _lastPosition ~/ 1000, _seekSliderValue); await _dbHelper.saveHistory(history); } - //_episode = null; + if (_playlist.isEmpty) { + _episode = null; + _backgroundAudioDuration = 0; + _backgroundAudioPosition = 0; + } } if (event is Map && event['position'] != null) { _backgroundAudioPosition = event['position'].inMilliseconds; @@ -1126,14 +1132,14 @@ class CustomAudioHandler extends BaseAudioHandler @override Future skipToNext() async { - if (queue.value.length == 0 || _stopAtEnd) { + if (queue.value.length == 1 || _stopAtEnd) { await Future.delayed(Duration(milliseconds: 200)); await stop(); } else { _autoSkip = false; await super.skipToNext(); _player.seekToNext(); - if (_isQueue && queue.value.isNotEmpty) { + if (_isQueue) { removeQueueItemAt(0); } } @@ -1141,7 +1147,7 @@ class CustomAudioHandler extends BaseAudioHandler @override Future play() async { - if (playing == null) { + if (playing == null || playing == false) { log('playing'); await super.play(); await _player.play(); @@ -1209,9 +1215,8 @@ class CustomAudioHandler extends BaseAudioHandler Future stop() async { await _player.stop(); - await _player.dispose(); - playbackState.add(playbackState.value - .copyWith(processingState: AudioProcessingState.loading)); + // await _player.dispose(); + _playlist.clear(); customEvent.add({'playerRunning': false}); await super.stop(); } diff --git a/pubspec.yaml b/pubspec.yaml index 86a1fd6..3ace3b5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,16 +51,12 @@ dependencies: workmanager: ^0.2.3 wc_flutter_share: ^0.4.0 xml: ^5.2.0 + audio_service: 0.18.0 just_audio: git: url: https://github.com/stonega/just_audio_origin.git ref: update path: just_audio - audio_service: - git: - url: https://github.com/ryanheise/audio_service.git - ref: one-isolate - path: audio_service flutter_downloader: git: url: https://github.com/stonega/flutter_downloader.git From b0d1639138f01de539b512077823f7eaaaf5e14c Mon Sep 17 00:00:00 2001 From: stonega Date: Wed, 6 Oct 2021 18:47:43 +0800 Subject: [PATCH 7/7] Update theme data --- lib/playlists/playlist_home.dart | 7 +- lib/playlists/playlist_page.dart | 1 - lib/state/setting_state.dart | 152 +++++++++++++++++-------------- lib/util/extension_helper.dart | 2 +- 4 files changed, 88 insertions(+), 74 deletions(-) diff --git a/lib/playlists/playlist_home.dart b/lib/playlists/playlist_home.dart index c7fb3e2..71afbcb 100644 --- a/lib/playlists/playlist_home.dart +++ b/lib/playlists/playlist_home.dart @@ -75,8 +75,8 @@ class _PlaylistHomeState extends State { return AnnotatedRegion( value: SystemUiOverlayStyle( systemNavigationBarIconBrightness: - Theme.of(context).accentColorBrightness, - statusBarIconBrightness: Theme.of(context).accentColorBrightness, + Theme.of(context).colorScheme.brightness, + statusBarIconBrightness: Theme.of(context).colorScheme.brightness, systemNavigationBarColor: Theme.of(context).primaryColor, ), child: WillPopScope( @@ -143,6 +143,9 @@ class _PlaylistHomeState extends State { playing ? audio.pauseAduio() : audio.resumeAudio(); + } else if (data.item1.isEmpty) { + Fluttertoast.showToast( + msg: 'Playlist is empty'); } else { context .read() diff --git a/lib/playlists/playlist_page.dart b/lib/playlists/playlist_page.dart index 8f3d365..b49b862 100644 --- a/lib/playlists/playlist_page.dart +++ b/lib/playlists/playlist_page.dart @@ -2,7 +2,6 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:file_picker/file_picker.dart'; import '../state/audio_state.dart'; import '../type/episodebrief.dart'; diff --git a/lib/state/setting_state.dart b/lib/state/setting_state.dart index 18df577..fa8e345 100644 --- a/lib/state/setting_state.dart +++ b/lib/state/setting_state.dart @@ -5,6 +5,7 @@ import 'dart:ui'; import 'package:connectivity/connectivity.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; @@ -18,43 +19,43 @@ import '../type/settings_backup.dart'; import 'download_state.dart'; void callbackDispatcher() { - Workmanager.executeTask((task, inputData) async { - var dbHelper = DBHelper(); - var podcastList = await dbHelper.getPodcastLocalAll(updateOnly: true); - //lastWork is a indicator for if the app was opened since last backgroundwork - //if the app wes opend,then the old marked new episode would be marked not new. - var lastWorkStorage = KeyValueStorage(lastWorkKey); - var lastWork = await lastWorkStorage.getInt(); - for (var podcastLocal in podcastList) { - await dbHelper.updatePodcastRss(podcastLocal, removeMark: lastWork); - developer.log('Refresh ${podcastLocal.title}'); - } - await FlutterDownloader.initialize(); - var downloader = AutoDownloader(); + Workmanager.executeTask((task, inputData) async { + var dbHelper = DBHelper(); + var podcastList = await dbHelper.getPodcastLocalAll(updateOnly: true); + //lastWork is a indicator for if the app was opened since last backgroundwork + //if the app wes opend,then the old marked new episode would be marked not new. + var lastWorkStorage = KeyValueStorage(lastWorkKey); + var lastWork = await lastWorkStorage.getInt(); + for (var podcastLocal in podcastList) { + await dbHelper.updatePodcastRss(podcastLocal, removeMark: lastWork); + developer.log('Refresh ${podcastLocal.title}'); + } + await FlutterDownloader.initialize(); + var downloader = AutoDownloader(); - var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey); - var autoDownloadNetwork = await autoDownloadStorage.getInt(); - var result = await Connectivity().checkConnectivity(); - if (autoDownloadNetwork == 1) { - var episodes = await dbHelper.getNewEpisodes('all'); - // For safety - if (episodes.length < 100 && episodes.length > 0) { - downloader.bindBackgroundIsolate(); - await downloader.startTask(episodes); - } - } else if (result == ConnectivityResult.wifi) { - var episodes = await dbHelper.getNewEpisodes('all'); - //For safety - if (episodes.length < 100 && episodes.length > 0) { - downloader.bindBackgroundIsolate(); - await downloader.startTask(episodes); - } + var autoDownloadStorage = KeyValueStorage(autoDownloadNetworkKey); + var autoDownloadNetwork = await autoDownloadStorage.getInt(); + var result = await Connectivity().checkConnectivity(); + if (autoDownloadNetwork == 1) { + var episodes = await dbHelper.getNewEpisodes('all'); + // For safety + if (episodes.length < 100 && episodes.length > 0) { + downloader.bindBackgroundIsolate(); + await downloader.startTask(episodes); } - await lastWorkStorage.saveInt(1); - var refreshstorage = KeyValueStorage(refreshdateKey); - await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch); - return Future.value(true); - }); + } else if (result == ConnectivityResult.wifi) { + var episodes = await dbHelper.getNewEpisodes('all'); + //For safety + if (episodes.length < 100 && episodes.length > 0) { + downloader.bindBackgroundIsolate(); + await downloader.startTask(episodes); + } + } + await lastWorkStorage.saveInt(1); + var refreshstorage = KeyValueStorage(refreshdateKey); + await refreshstorage.saveInt(DateTime.now().millisecondsSinceEpoch); + return Future.value(true); + }); } final showNotesFontStyles = [ @@ -139,42 +140,51 @@ class SettingState extends ChangeNotifier { ThemeMode get theme => _theme; ThemeData get lightTheme => ThemeData( - accentColorBrightness: Brightness.dark, - primaryColor: Colors.grey[100], - primaryColorLight: Colors.white, - primaryColorDark: Colors.grey[300], - dialogBackgroundColor: Colors.white, - backgroundColor: Colors.grey[100], - appBarTheme: AppBarTheme( - color: Colors.grey[100], - elevation: 0, - ), - textTheme: TextTheme( - bodyText2: TextStyle(fontSize: 15.0, fontWeight: FontWeight.normal), - ), - tabBarTheme: TabBarTheme( - labelColor: Colors.black, - unselectedLabelColor: Colors.grey[400], - ), - accentColor: _accentSetColor, - cursorColor: _accentSetColor, - textSelectionHandleColor: _accentSetColor, - toggleableActiveColor: _accentSetColor, - buttonTheme: ButtonThemeData( + colorScheme: ColorScheme.fromSwatch() + .copyWith(brightness: Brightness.light, secondary: _accentSetColor), + primaryColor: Colors.grey[100], + primaryColorLight: Colors.white, + primaryColorDark: Colors.grey[300], + dialogBackgroundColor: Colors.white, + backgroundColor: Colors.grey[100], + appBarTheme: AppBarTheme( + color: Colors.grey[100], + elevation: 0, + titleTextStyle: TextStyle(color: Colors.black), + iconTheme: IconThemeData(color: Colors.black), + systemOverlayStyle: SystemUiOverlayStyle.dark), + textTheme: TextTheme( + bodyText2: TextStyle(fontSize: 15.0, fontWeight: FontWeight.normal), + ), + tabBarTheme: TabBarTheme( + labelColor: Colors.black, + unselectedLabelColor: Colors.grey[400], + ), + textSelectionTheme: TextSelectionThemeData( + cursorColor: _accentSetColor, + selectionHandleColor: _accentSetColor, + ), + toggleableActiveColor: _accentSetColor, + buttonTheme: ButtonThemeData( height: 32, hoverColor: _accentSetColor.withAlpha(70), - splashColor: _accentSetColor.withAlpha(70))); + splashColor: _accentSetColor.withAlpha(70), + ), + ); + ThemeData get darkTheme => ThemeData.dark().copyWith( - accentColor: _accentSetColor, - primaryColorDark: Colors.grey[800], - scaffoldBackgroundColor: _realDark ? Colors.black87 : Color(0XFF212121), - primaryColor: _realDark ? Colors.black : Color(0XFF1B1B1B), - popupMenuTheme: PopupMenuThemeData() - .copyWith(color: _realDark ? Colors.grey[900] : null), - appBarTheme: AppBarTheme(elevation: 0), - buttonTheme: ButtonThemeData(height: 32), - dialogBackgroundColor: _realDark ? Colors.grey[900] : null, - cursorColor: _accentSetColor); + colorScheme: ColorScheme.fromSwatch() + .copyWith(brightness: Brightness.dark, secondary: _accentSetColor), + primaryColorDark: Colors.grey[800], + scaffoldBackgroundColor: _realDark ? Colors.black87 : Color(0XFF212121), + primaryColor: _realDark ? Colors.black : Color(0XFF1B1B1B), + popupMenuTheme: PopupMenuThemeData() + .copyWith(color: _realDark ? Colors.grey[900] : null), + appBarTheme: AppBarTheme( + elevation: 0, systemOverlayStyle: SystemUiOverlayStyle.light), + buttonTheme: ButtonThemeData(height: 32), + dialogBackgroundColor: _realDark ? Colors.grey[900] : null, + ); set setTheme(ThemeMode mode) { _theme = mode; @@ -558,8 +568,10 @@ class SettingState extends ChangeNotifier { .getBool(defaultValue: false); final deleteAfterPlayed = await KeyValueStorage(deleteAfterPlayedKey) .getBool(defaultValue: false); - final openPlaylistDefault = await _openPlaylistDefaultStorage.getBool(defaultValue: false); - final openAllPodcastDefault = await _openAllPodcastDefaultStorage.getBool(defaultValue: false); + final openPlaylistDefault = + await _openPlaylistDefaultStorage.getBool(defaultValue: false); + final openAllPodcastDefault = + await _openAllPodcastDefaultStorage.getBool(defaultValue: false); return SettingsBackup( theme: theme, diff --git a/lib/util/extension_helper.dart b/lib/util/extension_helper.dart index 1695b3f..c136e70 100644 --- a/lib/util/extension_helper.dart +++ b/lib/util/extension_helper.dart @@ -9,7 +9,7 @@ import '../generated/l10n.dart'; extension ContextExtension on BuildContext { Color get primaryColor => Theme.of(this).primaryColor; - Color get accentColor => Theme.of(this).accentColor; + Color get accentColor => Theme.of(this).colorScheme.secondary; Color get scaffoldBackgroundColor => Theme.of(this).scaffoldBackgroundColor; Color get primaryColorDark => Theme.of(this).primaryColorDark; Color get textColor => Theme.of(this).textTheme.bodyText1.color;