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: