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:flutter/foundation.dart'; import 'package:just_audio/just_audio.dart'; import '../local_storage/key_value_storage.dart'; import '../local_storage/sqflite_localpodcast.dart'; import '../type/episodebrief.dart'; import '../type/play_histroy.dart'; import '../type/playlist.dart'; MediaControl playControl = MediaControl( androidIcon: 'drawable/ic_stat_play_circle_filled', label: 'Play', action: MediaAction.play, ); MediaControl pauseControl = MediaControl( androidIcon: 'drawable/ic_stat_pause_circle_filled', label: 'Pause', action: MediaAction.pause, ); MediaControl skipToNextControl = MediaControl( androidIcon: 'drawable/baseline_skip_next_white_24', label: 'Next', action: MediaAction.skipToNext, ); MediaControl skipToPreviousControl = MediaControl( androidIcon: 'drawable/ic_action_skip_previous', label: 'Previous', action: MediaAction.skipToPrevious, ); MediaControl stopControl = MediaControl( androidIcon: 'drawable/baseline_close_white_24', label: 'Stop', action: MediaAction.stop, ); MediaControl forwardControl = MediaControl( androidIcon: 'drawable/baseline_fast_forward_white_24', label: 'forward', action: MediaAction.fastForward, ); MediaControl rewindControl = MediaControl( androidIcon: 'drawable/baseline_fast_rewind_white_24', label: 'rewind', action: MediaAction.rewind, ); void _audioPlayerTaskEntrypoint() async { AudioServiceBackground.run(() => AudioPlayerTask()); } /// Sleep timer mode. enum SleepTimerMode { endOfEpisode, timer, undefined } enum PlayerHeight { short, mid, tall } class AudioPlayerNotifier extends ChangeNotifier { final _dbHelper = DBHelper(); final _positionStorage = KeyValueStorage(audioPositionKey); final _autoPlayStorage = KeyValueStorage(autoPlayKey); final _autoSleepTimerStorage = KeyValueStorage(autoSleepTimerKey); final _defaultSleepTimerStorage = KeyValueStorage(defaultSleepTimerKey); final _autoSleepTimerModeStorage = KeyValueStorage(autoSleepTimerModeKey); final _autoSleepTimerStartStorage = KeyValueStorage(autoSleepTimerStartKey); final _autoSleepTimerEndStorage = KeyValueStorage(autoSleepTimerEndKey); final _fastForwardSecondsStorage = KeyValueStorage(fastForwardSecondsKey); final _rewindSecondsStorage = KeyValueStorage(rewindSecondsKey); final _playerHeightStorage = KeyValueStorage(playerHeightKey); final _speedStorage = KeyValueStorage(speedKey); final _skipSilenceStorage = KeyValueStorage(skipSilenceKey); final _boostVolumeStorage = KeyValueStorage(boostVolumeKey); final _volumeGainStorage = KeyValueStorage(volumeGainKey); final _markListenedAfterSkipStorage = KeyValueStorage(markListenedAfterSkipKey); final _playlistsStorgae = KeyValueStorage(playlistsAllKey); final _playerStateStorage = KeyValueStorage(playerStateKey); /// Playing episdoe. EpisodeBrief _episode; /// Playlists include queue and playlists created by user. List _playlists; /// Playing playlist. Playlist _playlist; /// Queue is the first playlist by default. Playlist get _queue => _playlists.first; /// Player state. AudioProcessingState _audioState = AudioProcessingState.none; /// Player playing. bool _playing = false; /// Fastforward second. int _fastForwardSeconds = 0; /// Rewind seconds. int _rewindSeconds = 0; /// No slide, set true if slide on seekbar. bool _noSlide = true; /// Current episode duration. int _backgroundAudioDuration = 0; /// Current episode positin. int _backgroundAudioPosition = 0; /// Erroe maeesage. String _remoteErrorMessage; /// Seekbar value, min 0, max 1.0. double _seekSliderValue = 0.0; /// Record plyaer position. int _lastPostion = 0; /// Set true if sleep timer mode is end of episode. bool _stopOnComplete = false; /// Sleep timer timer. Timer _stopTimer; /// Sleep timer time left. int _timeLeft = 0; /// Start sleep timer. bool _startSleepTimer = false; /// Control sleep timer anamation. double _switchValue = 0; /// Sleep timer mode. SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined; //Auto stop at the end of episode when you start play at scheduled time. bool _autoSleepTimer; //set autoplay episode in playlist bool _autoPlay; /// Datetime now. DateTime _current; /// Current position. int _currentPosition; /// Current speed. double _currentSpeed = 1; ///Update episode card when setting changed bool _episodeState = false; /// Player height. PlayerHeight _playerHeight; /// Player skip silence. bool _skipSilence; /// Boost volumn bool _boostVolume; /// Boost volume gain. int _volumeGain; // ignore: prefer_final_fields bool _playerRunning = false; bool _markListened; @override void addListener(VoidCallback listener) { super.addListener(listener); _initAudioData(); AudioService.connect(); } @override void dispose() async { await AudioService.disconnect(); super.dispose(); } /// Audio playing state. AudioProcessingState get audioState => _audioState; int get backgroundAudioDuration => _backgroundAudioDuration; int get backgroundAudioPosition => _backgroundAudioPosition; double get seekSliderValue => _seekSliderValue; String get remoteErrorMessage => _remoteErrorMessage; bool get playerRunning => _playerRunning; bool get buffering => _audioState != AudioProcessingState.ready; EpisodeBrief get episode => _episode; /// Playlist provider. int get lastPositin => _lastPostion; List get playlists => _playlists; Playlist get queue => _playlists.first; bool get playing => _playing; Playlist get playlist => _playlist; /// Player control. bool get stopOnComplete => _stopOnComplete; bool get startSleepTimer => _startSleepTimer; SleepTimerMode get sleepTimerMode => _sleepTimerMode; int get timeLeft => _timeLeft; double get switchValue => _switchValue; double get currentSpeed => _currentSpeed; bool get episodeState => _episodeState; bool get autoSleepTimer => _autoSleepTimer; int get fastForwardSeconds => _fastForwardSeconds; int get rewindSeconds => _rewindSeconds; PlayerHeight get playerHeight => _playerHeight; bool get skipSilence => _skipSilence; bool get boostVolume => _boostVolume; int get volumeGain => _volumeGain; set setSwitchValue(double value) { _switchValue = value; notifyListeners(); } set setEpisodeState(bool boo) { _episodeState = !_episodeState; notifyListeners(); } set setPlayerHeight(PlayerHeight mode) { _playerHeight = mode; notifyListeners(); _savePlayerHeight(); } Future _initAudioData() async { var index = await _playerHeightStorage.getInt(defaultValue: 0); _playerHeight = PlayerHeight.values[index]; _currentSpeed = await _speedStorage.getDouble(defaultValue: 1.0); _skipSilence = await _skipSilenceStorage.getBool(defaultValue: false); _boostVolume = await _boostVolumeStorage.getBool(defaultValue: false); _volumeGain = await _volumeGainStorage.getInt(defaultValue: 3000); } Future _savePlayerHeight() async { await _playerHeightStorage.saveInt(_playerHeight.index); } Future _getAutoPlay() async { var i = await _autoPlayStorage.getInt(); _autoPlay = i == 0; } Future _getAutoSleepTimer() async { var i = await _autoSleepTimerStorage.getInt(); _autoSleepTimer = i == 1; } Future initPlaylist() async { var playlistEntities = await _playlistsStorgae.getPlaylists(); _playlists = [ for (var entity in playlistEntities) Playlist.fromEntity(entity) ]; await _playlists.first.getPlaylist(); await _getAutoPlay(); var state = await _playerStateStorage.getPlayerState(); if (state[0] != '') { _playlist = _playlists.firstWhere((p) => p.id == state[0], orElse: () => _playlists.first); } else { _playlist = _playlists.first; } await _playlist.getPlaylist(); if (state[1] != '') { _episode = await _dbHelper.getRssItemWithUrl(state[1]); } else { _episode = _playlist.episodes.isNotEmpty ? _playlist.episodes.first : null; } _lastPostion = int.parse(state[2] ?? '0'); /// Save plays history if app is closed accidentally. /// if (_lastPostion > 0 && _queue.episodes.isNotEmpty) { /// final episode = _queue.episodes.first; /// final duration = episode.duration * 1000; /// final seekValue = duration != 0 ? _lastPostion / duration : 1.0; /// final history = PlayHistory( /// episode.title, episode.enclosureUrl, _lastPostion ~/ 1000, seekValue); /// await _dbHelper.saveHistory(history); /// } await KeyValueStorage(lastWorkKey).saveInt(0); } Future playFromLastPosition() async { if (_playlist.episodes.isNotEmpty) { await _playlist.getPlaylist(); if (_episode == null || !_playlist.episodes.contains(_episode)) _episode = _playlist.episodes.first; _audioState = AudioProcessingState.none; _backgroundAudioDuration = 0; _backgroundAudioPosition = 0; _seekSliderValue = 0; _playerRunning = true; notifyListeners(); _startAudioService(_playlist, position: _lastPostion ?? 0, index: _playlist.episodes.indexOf(_episode)); } } Future playlistLoad(Playlist playlist) async { var p = playlist; if (playlist.name != 'Queue') { await updatePlaylist(p, updateEpisodes: true); } _playlist = p; notifyListeners(); if (playlist.episodeList.isNotEmpty) { if (playerRunning) { AudioService.customAction('setIsQueue', playlist.name == 'Queue'); AudioService.customAction('changeQueue', [for (var e in p.episodes) jsonEncode(e.toMediaItem().toJson())]); } else { _backgroundAudioDuration = 0; _backgroundAudioPosition = 0; _seekSliderValue = 0; _episode = playlist.episodes.first; _audioState = AudioProcessingState.none; _playerRunning = true; notifyListeners(); _startAudioService(_playlist, position: 0, index: 0); } } } Future episodeLoad(EpisodeBrief episode, {int startPosition = 0}) async { final episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); //TODO load episode from last position when player running if (playerRunning) { final history = PlayHistory(_episode.title, _episode.enclosureUrl, backgroundAudioPosition ~/ 1000, seekSliderValue); await _dbHelper.saveHistory(history); _queue.addToPlayListAt(episodeNew, 0); await updatePlaylist(_queue); if (_playlist.name != 'Queue') { AudioService.customAction('setIsQueue', true); AudioService.customAction('changeQueue', [ for (var e in _queue.episodes) jsonEncode(e.toMediaItem().toJson()) ]); _playlist = _queue; } await AudioService.addQueueItemAt(episodeNew.toMediaItem(), 0); if (startPosition > 0) { await AudioService.seekTo(Duration(milliseconds: startPosition)); } _remoteErrorMessage = null; notifyListeners(); if (episodeNew.isNew == 1) { await _dbHelper.removeEpisodeNewMark(episodeNew.enclosureUrl); } } else { await _queue.getPlaylist(); _queue.addToPlayListAt(episodeNew, 0); _backgroundAudioDuration = 0; _backgroundAudioPosition = 0; _seekSliderValue = 0; _episode = episodeNew; _playerRunning = true; notifyListeners(); _startAudioService(_queue, position: startPosition); } } Future loadEpisodeFromPlaylist(EpisodeBrief episode) async { if (_playlist.episodes.contains(episode)) { var index = _playlist.episodes.indexOf(episode); await AudioService.customAction('changeIndex', index); } } Future _startAudioService(Playlist playlist, {int index = 0, int position = 0}) async { _stopOnComplete = false; _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); _rewindSeconds = await _rewindSecondsStorage.getInt(defaultValue: 10); /// Get if auto mark listened after skip _markListened = 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()); } } else { await AudioService.addQueueItem(playlist.episodes[index].toMediaItem()); } //Check auto sleep timer setting await _getAutoSleepTimer(); if (_autoSleepTimer) { var startTime = await _autoSleepTimerStartStorage.getInt(defaultValue: 1380); var endTime = await _autoSleepTimerEndStorage.getInt(defaultValue: 360); var currentTime = DateTime.now().hour * 60 + DateTime.now().minute; if ((startTime > endTime && (currentTime > startTime || currentTime < endTime)) || ((startTime < endTime) && (currentTime > startTime && currentTime < endTime))) { var mode = await _autoSleepTimerModeStorage.getInt(); _sleepTimerMode = SleepTimerMode.values[mode]; var defaultTimer = await _defaultSleepTimerStorage.getInt(defaultValue: 30); sleepTimer(defaultTimer); } } /// Set player speed. if (_currentSpeed != 1.0) { await AudioService.customAction('setSpeed', _currentSpeed); } /// Set slipsilence. if (_skipSilence) { await AudioService.customAction('setSkipSilence', skipSilence); } /// Set boostValome. if (_boostVolume) { await AudioService.customAction( 'setBoostVolume', [_boostVolume, _volumeGain]); } await AudioService.play(); AudioService.currentMediaItemStream .where((event) => event != null) .listen((item) async { var episode = await _dbHelper.getRssItemWithMediaId(item.id); _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; } notifyListeners(); } else { // _queue.playlist.removeAt(0); AudioService.skipToNext(); } }); AudioService.playbackStateStream .distinct() .where((event) => event != null) .listen((event) async { _current = DateTime.now(); _audioState = event.processingState; _playing = event?.playing; _currentSpeed = event.speed; _currentPosition = event.currentPosition.inMilliseconds ?? 0; if (_audioState == AudioProcessingState.stopped) { if (_switchValue > 0) _switchValue = 0; } /// Get error state. if (_audioState == AudioProcessingState.error) { _remoteErrorMessage = 'Network Error'; } /// Reset error state. if (_audioState != AudioProcessingState.error) { _remoteErrorMessage = null; } notifyListeners(); }); AudioService.customEventStream.distinct().listen((event) async { if (event is String) { if (_playlist.name == 'Queue' && _queue.episodes.isNotEmpty && _queue.episodes.first.title == event) { _queue.delFromPlaylist(_episode); } _lastPostion = 0; notifyListeners(); // await _positionStorage.saveInt(_lastPostion); await _playerStateStorage.savePlayerState( _playlist.id, _episode.enclosureUrl, _lastPostion); var history; if (_markListened) { history = PlayHistory(_episode.title, _episode.enclosureUrl, _backgroundAudioPosition ~/ 1000, 1); } else { history = PlayHistory(_episode.title, _episode.enclosureUrl, _backgroundAudioPosition ~/ 1000, _seekSliderValue); } await _dbHelper.saveHistory(history); } if (event is Map && event['playerRunning'] == false && _playerRunning) { _playerRunning = false; notifyListeners(); if (_lastPostion > 0) { final history = PlayHistory(_episode.title, _episode.enclosureUrl, _lastPostion ~/ 1000, _seekSliderValue); await _dbHelper.saveHistory(history); } //_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 (_backgroundAudioDuration != null && _backgroundAudioDuration != 0 && _backgroundAudioPosition != null) { _seekSliderValue = _backgroundAudioPosition / _backgroundAudioDuration ?? 0; } else { _seekSliderValue = 0; } if (_backgroundAudioPosition > 0 && _backgroundAudioPosition < _backgroundAudioDuration) { _lastPostion = _backgroundAudioPosition; // _positionStorage.saveInt(_lastPostion); _playerStateStorage.savePlayerState( _playlist.id, _episode.enclosureUrl, _lastPostion); } notifyListeners(); } if (_audioState == AudioProcessingState.stopped) { timer.cancel(); } }); } /// Playlists management. Future addToPlaylist(EpisodeBrief episode) async { var episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); if (episodeNew.isNew == 1) { await _dbHelper.removeEpisodeNewMark(episodeNew.enclosureUrl); } if (!_queue.episodes.contains(episodeNew)) { if (playerRunning && _playlist.name == 'Queue') { await AudioService.addQueueItem(episodeNew.toMediaItem()); } _queue.addToPlayList(episodeNew); await updatePlaylist(_queue, updateEpisodes: false); } } Future addToPlaylistAt(EpisodeBrief episode, int index) async { var episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); if (episodeNew.isNew == 1) { await _dbHelper.removeEpisodeNewMark(episodeNew.enclosureUrl); } if (playerRunning && _playlist.name == 'Queue') { await AudioService.addQueueItemAt(episodeNew.toMediaItem(), index); } _queue.addToPlayListAt(episodeNew, index); await updatePlaylist(_queue, updateEpisodes: false); } Future addNewEpisode(List group) async { var newEpisodes = []; if (group.isEmpty) { newEpisodes = await _dbHelper.getRecentNewRssItem(); } else { newEpisodes = await _dbHelper.getGroupNewRssItem(group); } if (newEpisodes.length > 0 && newEpisodes.length < 100) { for (var episode in newEpisodes) { await addToPlaylist(episode); } } if (group.isEmpty) { await _dbHelper.removeAllNewMark(); } else { await _dbHelper.removeGroupNewMark(group); } } Future updateMediaItem(EpisodeBrief episode) async { if (episode.enclosureUrl == episode.mediaId && _episode != episode) { var episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); _playlist.updateEpisode(episodeNew); } } Future delFromPlaylist(EpisodeBrief episode) async { var episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); if (playerRunning && _playlist.name == 'Queue') { await AudioService.removeQueueItem(episodeNew.toMediaItem()); } var index = _queue.delFromPlaylist(episodeNew); if (index == 0) { _lastPostion = 0; await _positionStorage.saveInt(0); } updatePlaylist(_queue, updateEpisodes: false); return index; } Future reorderPlaylist(int oldIndex, int newIndex) async { var episode = _queue.episodes[oldIndex]; if (playerRunning && _playlist.name == 'Queue') { await AudioService.removeQueueItem(episode.toMediaItem()); await AudioService.addQueueItemAt(episode.toMediaItem(), newIndex); } _queue.addToPlayListAt(episode, newIndex); updatePlaylist(_queue, updateEpisodes: false); if (newIndex == 0) { _lastPostion = 0; await _positionStorage.saveInt(0); } } Future moveToTop(EpisodeBrief episode) async { await delFromPlaylist(episode); if (playerRunning && _playlist.name == 'Queue') { final episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); await AudioService.addQueueItemAt(episodeNew.toMediaItem(), 1); _queue.addToPlayListAt(episode, 1, existed: false); } else { _queue.addToPlayListAt(episode, 0, existed: false); _lastPostion = 0; _positionStorage.saveInt(_lastPostion); } updatePlaylist(_queue, updateEpisodes: false); return true; } void addPlaylist(Playlist playlist) { _playlists = [..._playlists, playlist]; notifyListeners(); _savePlaylists(); } void deletePlaylist(Playlist playlist) { _playlists = [ for (var p in _playlists) if (p != playlist) p ]; if (_playlist == playlist && !_playerRunning) { _playlist = _queue; _episode = _queue.episodes.first; } notifyListeners(); _savePlaylists(); } void addEpisodesToPlaylist(Playlist playlist, {List episodes}) { for (var e in episodes) { playlist.addToPlayList(e); if (playerRunning && playlist == _playlist) { AudioService.addQueueItem(e.toMediaItem()); } } updatePlaylist(playlist, updateEpisodes: false); } void removeEpisodeFromPlaylist(Playlist playlist, {List episodes}) { for (var e in episodes) { playlist.delFromPlaylist(e); if (playerRunning && playlist == _playlist) { AudioService.removeQueueItem(e.toMediaItem()); } } updatePlaylist(playlist, updateEpisodes: false); } void reorderEpisodesInPlaylist(Playlist playlist, {int oldIndex, int newIndex}) async { playlist.reorderPlaylist(oldIndex, newIndex); if (playerRunning && playlist == _playlist) { if (newIndex > oldIndex) { newIndex -= 1; } await AudioService.removeQueueItem(episode.toMediaItem()); await AudioService.addQueueItemAt(episode.toMediaItem(), newIndex); } updatePlaylist(playlist, updateEpisodes: false); } void clearPlaylist(Playlist playlist) { playlist.clear(); updatePlaylist(playlist, updateEpisodes: false); } Future updatePlaylist(Playlist playlist, {bool updateEpisodes = true}) async { if (updateEpisodes) await playlist.getPlaylist(); _playlists = [for (var p in _playlists) p.id == playlist.id ? playlist : p]; if (_playlist.id == playlist.id && !_playerRunning) { _playlist = _playlists.firstWhere((e) => e.id == _playlist.id); notifyListeners(); } _savePlaylists(); } bool playlistExisted(String name) { for (var p in _playlists) { if (p.name == name) return true; } return false; } void _updateAllPlaylists() { _playlists = [..._playlists]; notifyListeners(); _savePlaylists(); } Future _savePlaylists() async { await _playlistsStorgae .savePlaylists([for (var p in _playlists) p.toEntity()]); } /// Audio control. Future pauseAduio() async { await AudioService.pause(); } Future resumeAudio() async { _remoteErrorMessage = null; notifyListeners(); if (_audioState != AudioProcessingState.connecting && _audioState != AudioProcessingState.none) { AudioService.play(); } } Future playNext() async { _remoteErrorMessage = null; await AudioService.skipToNext(); notifyListeners(); } Future forwardAudio(int s) async { var pos = _backgroundAudioPosition + s * 1000; await AudioService.seekTo(Duration(milliseconds: pos)); } Future fastForward() async { await AudioService.fastForward(); } Future rewind() async { await AudioService.rewind(); } Future seekTo(int position) async { if (_audioState != AudioProcessingState.connecting && _audioState != AudioProcessingState.none) { await AudioService.seekTo(Duration(milliseconds: position)); } } Future sliderSeek(double val) async { if (_audioState != AudioProcessingState.connecting && _audioState != AudioProcessingState.none) { _noSlide = false; _seekSliderValue = val; notifyListeners(); _currentPosition = (val * _backgroundAudioDuration).toInt(); await AudioService.seekTo(Duration(milliseconds: _currentPosition)); _noSlide = true; } } /// Set player speed. Future setSpeed(double speed) async { await AudioService.customAction('setSpeed', 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(); } set setVolumeGain(int volumeGain) { _volumeGain = volumeGain; if (_playerRunning && _boostVolume) { 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); } //Set sleep timer void sleepTimer(int mins) { if (_sleepTimerMode == SleepTimerMode.timer) { _startSleepTimer = true; _switchValue = 1; notifyListeners(); _timeLeft = mins * 60; Timer.periodic(Duration(seconds: 1), (timer) { if (_timeLeft == 0) { timer.cancel(); notifyListeners(); } else { _timeLeft = _timeLeft - 1; notifyListeners(); } }); _stopTimer = Timer(Duration(minutes: mins), () { _stopOnComplete = false; _startSleepTimer = false; _switchValue = 0; if (_playerRunning) { AudioService.stop(); } notifyListeners(); // AudioService.disconnect(); }); } else if (_sleepTimerMode == SleepTimerMode.endOfEpisode) { _stopOnComplete = true; _switchValue = 1; notifyListeners(); if (_queue.episodes.length > 1 && _autoPlay) { AudioService.customAction('stopAtEnd'); } } } set setSleepTimerMode(SleepTimerMode timer) { _sleepTimerMode = timer; notifyListeners(); } //Cancel sleep timer void cancelTimer() { if (_sleepTimerMode == SleepTimerMode.timer) { _stopTimer.cancel(); _timeLeft = 0; _startSleepTimer = false; _switchValue = 0; notifyListeners(); } else if (_sleepTimerMode == SleepTimerMode.endOfEpisode) { AudioService.customAction('cancelStopAtEnd'); _switchValue = 0; _stopOnComplete = false; notifyListeners(); } } /// Save player state. Future _savePlayerPosiont() {} } class AudioPlayerTask extends BackgroundAudioTask { final cacheStorage = KeyValueStorage(cacheMaxKey); final layoutStorage = KeyValueStorage(notificationLayoutKey); final List _queue = []; final AudioPlayer _audioPlayer = 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; MediaItem get mediaItem => _queue.length > 0 ? _queue[_index] : null; StreamSubscription _playerStateSubscription; StreamSubscription _eventSubscription; @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; } }); } void _handlePlaybackCompleted() async { if (hasNext) { onSkipToNext(); } else { _audioPlayer.stop(); _queue.removeAt(0); await AudioServiceBackground.setQueue(_queue); onStop(); } } void _handleInterruption(AudioSession session) async { session.interruptionEventStream.listen((event) { if (event.begin) { switch (event.type) { case AudioInterruptionType.pause: if (_playing) { onPause(); _interrupted = true; } break; case AudioInterruptionType.duck: if (_playing) { onPause(); _interrupted = true; } break; case AudioInterruptionType.unknown: if (_playing) { onPause(); _interrupted = true; } break; } } else { switch (event.type) { case AudioInterruptionType.pause: if (!_playing && _interrupted) { onPlay(); } break; case AudioInterruptionType.duck: if (!_playing && _interrupted) { onPlay(); } break; case AudioInterruptionType.unknown: break; } _interrupted = false; } }); session.becomingNoisyEventStream.listen((_) { if (_playing) onPause(); }); } void playPause() { if (AudioServiceBackground.state.playing) { onPause(); } else { onPlay(); } } @override Future onSkipToNext() async { _skipState = AudioProcessingState.skippingToNext; _playing = false; await _audioPlayer.stop(); AudioServiceBackground.sendCustomEvent(mediaItem.title); if (_isQueue) { if (_queue.length > 0) { _queue.removeAt(0); } await AudioServiceBackground.setQueue(_queue); } else { _index += 1; } if (_queue.length == 0 || _stopAtEnd) { _skipState = null; await Future.delayed(Duration(milliseconds: 200)); await onStop(); } else { // await AudioServiceBackground.setQueue(_queue); await AudioServiceBackground.setMediaItem(mediaItem); await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax); var duration = await _audioPlayer.durationFuture; if (duration != null) { await AudioServiceBackground.setMediaItem( mediaItem.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 _playFromStart() async { _playing = true; _session.setActive(true); if (mediaItem.extras['skipSecondsStart'] > 0 || mediaItem.extras['skipSecondsEnd'] > 0) { _audioPlayer .seek(Duration(seconds: mediaItem.extras['skipSecondsStart'])); } if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting || _audioPlayer.playbackEvent.state != AudioPlaybackState.none) { try { _audioPlayer.play(); } catch (e) { _setState(processingState: AudioProcessingState.error); } } } @override Future onPause() async { if (_skipState == null) { if (_playing == null) { } else if (_playing) { _playing = false; _audioPlayer.pause(); } } } @override Future onSeekTo(Duration position) async { if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting || _audioPlayer.playbackEvent.state != AudioPlaybackState.none) { await _audioPlayer.seek(position); } } @override Future onClick(MediaButton button) async { switch (button) { case MediaButton.media: if (AudioServiceBackground.state?.playing == true) { await onPause(); } else { await onPlay(); } break; case MediaButton.next: await onFastForward(); break; case MediaButton.previous: await onRewind(); break; } } Future _seekRelative(Duration offset) async { var newPosition = _audioPlayer.playbackEvent.position + offset; if (newPosition < Duration.zero) newPosition = Duration.zero; onSeekTo(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(); } @override Future onTaskRemoved() async { await onStop(); } @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 { 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)); _playFromStart(); //onPlay(); } else { _queue.insert(index, mediaItem); if (index < _index) _index += 1; await AudioServiceBackground.setQueue(_queue); } } @override Future onFastForward() async { await _seekRelative(fastForwardInterval); } @override Future onRewind() async { await _seekRelative(-rewindInterval); } @override Future onCustomAction(funtion, argument) async { switch (funtion) { case 'stopAtEnd': _stopAtEnd = true; break; case 'cancelStopAtEnd': _stopAtEnd = false; break; case 'setSpeed': await _audioPlayer.setSpeed(argument); break; case 'setSkipSilence': await _setSkipSilence(argument); break; case 'setBoostVolume': await _setBoostVolume(argument[0], argument[1]); break; case 'setIsQueue': _isQueue = argument; break; case 'changeQueue': await _changeQueue(argument); break; case 'changeIndex': await _changeIndex(argument); break; } } Future _changeQueue(List items) async { var queue = [for (var i in items) MediaItem.fromJson(json.decode(i))]; await _audioPlayer.stop(); AudioServiceBackground.sendCustomEvent(mediaItem.title); _queue.clear(); _queue.addAll(queue); _index = 0; 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)); _playFromStart(); } Future _changeIndex(int index) async { await _audioPlayer.stop(); AudioServiceBackground.sendCustomEvent(mediaItem.title); _index = index; await AudioServiceBackground.setMediaItem(mediaItem); await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax); var duration = await _audioPlayer.durationFuture ?? Duration.zero; AudioServiceBackground.setMediaItem(mediaItem.copyWith(duration: duration)); _playFromStart(); } Future _setSkipSilence(bool boo) async { await _audioPlayer.setSkipSilence(boo); var duration = await _audioPlayer.durationFuture ?? Duration.zero; AudioServiceBackground.setMediaItem(mediaItem.copyWith(duration: duration)); } 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, ); } List _getControls(int index) { switch (index) { case 0: return [ _playing ? pauseControl : playControl, forwardControl, skipToNextControl, stopControl ]; break; case 1: return [ _playing ? pauseControl : playControl, rewindControl, skipToNextControl, stopControl ]; break; case 2: return [ rewindControl, _playing ? pauseControl : playControl, forwardControl, stopControl ]; break; default: return [ _playing ? pauseControl : playControl, forwardControl, skipToNextControl, stopControl ]; break; } } }