Support audio cache

This commit is contained in:
stonegate 2020-04-28 01:26:33 +08:00
parent e8cedbce75
commit ce698dd548
9 changed files with 225 additions and 153 deletions

View File

@ -137,8 +137,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined; SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined;
//set autoplay episode in playlist //set autoplay episode in playlist
bool _autoPlay = true; bool _autoPlay = true;
//Set auto add episodes to playlist //TODO Set auto add episodes to playlist
bool _autoAdd = false; //bool _autoAdd = false;
DateTime _current; DateTime _current;
int _currentPosition; int _currentPosition;
double _currentSpeed = 1; double _currentSpeed = 1;
@ -159,7 +159,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
bool get startSleepTimer => _startSleepTimer; bool get startSleepTimer => _startSleepTimer;
SleepTimerMode get sleepTimerMode => _sleepTimerMode; SleepTimerMode get sleepTimerMode => _sleepTimerMode;
bool get autoPlay => _autoPlay; bool get autoPlay => _autoPlay;
bool get autoAdd => _autoAdd;
int get timeLeft => _timeLeft; int get timeLeft => _timeLeft;
double get switchValue => _switchValue; double get switchValue => _switchValue;
double get currentSpeed => _currentSpeed; double get currentSpeed => _currentSpeed;
@ -177,28 +176,13 @@ class AudioPlayerNotifier extends ChangeNotifier {
Future _getAutoPlay() async { Future _getAutoPlay() async {
int i = await autoPlayStorage.getInt(); int i = await autoPlayStorage.getInt();
_autoAdd = i == 0 ? true : false; _autoPlay = i == 0 ? true : false;
} }
Future _setAutoPlay() async { Future _setAutoPlay() async {
await autoPlayStorage.saveInt(_autoPlay ? 1 : 0); await autoPlayStorage.saveInt(_autoPlay ? 1 : 0);
} }
set autoAddSwitch(bool boo) {
_autoAdd = boo;
notifyListeners();
_setAutoAdd();
}
//Future _getAutoAdd() async {
// int i = await autoAddStorage.getInt();
// _autoAdd = i == 0 ? false : true;
//}
Future _setAutoAdd() async {
await autoAddStorage.saveInt(_autoAdd ? 1 : 0);
}
set setSleepTimerMode(SleepTimerMode timer) { set setSleepTimerMode(SleepTimerMode timer) {
_sleepTimerMode = timer; _sleepTimerMode = timer;
notifyListeners(); notifyListeners();
@ -270,6 +254,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
if (!AudioService.connected) { if (!AudioService.connected) {
await AudioService.connect(); await AudioService.connect();
} }
await AudioService.start( await AudioService.start(
backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint, backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
androidNotificationChannelName: 'Tsacdop', androidNotificationChannelName: 'Tsacdop',
@ -278,7 +263,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
enableQueue: true, enableQueue: true,
androidStopOnRemoveTask: true, androidStopOnRemoveTask: true,
androidStopForegroundOnPause: true); androidStopForegroundOnPause: true);
if (_autoPlay) { if (_autoPlay) {
await Future.forEach(_queue.playlist, (episode) async { await Future.forEach(_queue.playlist, (episode) async {
await AudioService.addQueueItem(episode.toMediaItem()); await AudioService.addQueueItem(episode.toMediaItem());
@ -572,13 +556,15 @@ class AudioPlayerNotifier extends ChangeNotifier {
} }
class AudioPlayerTask extends BackgroundAudioTask { class AudioPlayerTask extends BackgroundAudioTask {
KeyValueStorage cacheStorage = KeyValueStorage(cacheMaxKey);
List<MediaItem> _queue = []; List<MediaItem> _queue = [];
AudioPlayer _audioPlayer = AudioPlayer(); AudioPlayer _audioPlayer = AudioPlayer();
Completer _completer = Completer(); Completer _completer = Completer();
BasicPlaybackState _skipState; BasicPlaybackState _skipState;
bool _playing; bool _playing;
bool _stopAtEnd; bool _stopAtEnd;
int cacheMax;
bool get hasNext => _queue.length > 0; bool get hasNext => _queue.length > 0;
MediaItem get mediaItem => _queue.length > 0 ? _queue.first : null; MediaItem get mediaItem => _queue.length > 0 ? _queue.first : null;
@ -605,6 +591,8 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override @override
Future<void> onStart() async { Future<void> onStart() async {
_stopAtEnd = false; _stopAtEnd = false;
int cache = await cacheStorage.getInt();
cacheMax = cache == 0 ? 500 * 1024 * 1024 : cache;
var playerStateSubscription = _audioPlayer.playbackStateStream var playerStateSubscription = _audioPlayer.playbackStateStream
.where((state) => state == AudioPlaybackState.completed) .where((state) => state == AudioPlaybackState.completed)
.listen((state) { .listen((state) {
@ -664,7 +652,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
} else { } else {
await AudioServiceBackground.setQueue(_queue); await AudioServiceBackground.setQueue(_queue);
await AudioServiceBackground.setMediaItem(mediaItem); await AudioServiceBackground.setMediaItem(mediaItem);
await _audioPlayer.setUrl(mediaItem.id); await _audioPlayer.setUrl(mediaItem.id, cacheMax);
print(mediaItem.title); print(mediaItem.title);
Duration duration = await _audioPlayer.durationFuture; Duration duration = await _audioPlayer.durationFuture;
if (duration != null) if (duration != null)
@ -687,7 +675,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
if (_playing == null) { if (_playing == null) {
_playing = true; _playing = true;
// await AudioServiceBackground.setQueue(_queue); // await AudioServiceBackground.setQueue(_queue);
await _audioPlayer.setUrl(mediaItem.id); await _audioPlayer.setUrl(mediaItem.id, cacheMax);
var duration = await _audioPlayer.durationFuture; var duration = await _audioPlayer.durationFuture;
if (duration != null) if (duration != null)
await AudioServiceBackground.setMediaItem( await AudioServiceBackground.setMediaItem(
@ -777,7 +765,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
_queue.insert(0, mediaItem); _queue.insert(0, mediaItem);
await AudioServiceBackground.setQueue(_queue); await AudioServiceBackground.setQueue(_queue);
await AudioServiceBackground.setMediaItem(mediaItem); await AudioServiceBackground.setMediaItem(mediaItem);
await _audioPlayer.setUrl(mediaItem.id); await _audioPlayer.setUrl(mediaItem.id, cacheMax);
Duration duration = await _audioPlayer.durationFuture ?? Duration.zero; Duration duration = await _audioPlayer.durationFuture ?? Duration.zero;
AudioServiceBackground.setMediaItem( AudioServiceBackground.setMediaItem(
mediaItem.copyWith(duration: duration.inMilliseconds)); mediaItem.copyWith(duration: duration.inMilliseconds));

View File

@ -4,7 +4,7 @@ import 'package:tsacdop/util/custompaint.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
const String version = '0.2.0'; const String version = '0.2.0+1';
class AboutApp extends StatelessWidget { class AboutApp extends StatelessWidget {
_launchUrl(String url) async { _launchUrl(String url) async {

View File

@ -18,92 +18,8 @@ import 'package:tsacdop/util/pageroute.dart';
import 'package:tsacdop/util/colorize.dart'; import 'package:tsacdop/util/colorize.dart';
import 'package:tsacdop/util/context_extension.dart'; import 'package:tsacdop/util/context_extension.dart';
import 'package:tsacdop/util/custompaint.dart'; import 'package:tsacdop/util/custompaint.dart';
import '../util/customslider.dart';
class MyRectangularTrackShape extends RectangularSliderTrackShape {
Rect getPreferredRect({
@required RenderBox parentBox,
Offset offset = Offset.zero,
@required SliderThemeData sliderTheme,
bool isEnabled = false,
bool isDiscrete = false,
}) {
final double trackHeight = sliderTheme.trackHeight;
final double trackLeft = offset.dx;
final double trackTop =
offset.dy + (parentBox.size.height - trackHeight) / 2;
final double trackWidth = parentBox.size.width;
return Rect.fromLTWH(trackLeft - 5, trackTop, trackWidth, trackHeight);
}
}
class MyRoundSliderThumpShape extends SliderComponentShape {
const MyRoundSliderThumpShape({
this.enabledThumbRadius = 10.0,
this.disabledThumbRadius,
this.thumbCenterColor,
});
final Color thumbCenterColor;
final double enabledThumbRadius;
final double disabledThumbRadius;
double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.fromRadius(
isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
}
@override
void paint(
PaintingContext context,
Offset center, {
Animation<double> activationAnimation,
@required Animation<double> enableAnimation,
bool isDiscrete,
TextPainter labelPainter,
RenderBox parentBox,
@required SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
}) {
final Canvas canvas = context.canvas;
final Tween<double> radiusTween = Tween<double>(
begin: _disabledThumbRadius,
end: enabledThumbRadius,
);
// final ColorTween colorTween = ColorTween(
// begin: sliderTheme.disabledThumbColor,
// end: sliderTheme.thumbColor,
// );
canvas.drawCircle(
center,
radiusTween.evaluate(enableAnimation),
Paint()
..color = thumbCenterColor
..style = PaintingStyle.fill
..strokeWidth = 2,
);
canvas.drawRect(
Rect.fromLTRB(
center.dx - 10, center.dy + 10, center.dx + 10, center.dy - 10),
Paint()
..color = Colors.white
..style = PaintingStyle.fill
..strokeWidth = 10,
);
canvas.drawLine(
Offset(center.dx - 5, center.dy - 2),
Offset(center.dx + 5, center.dy + 2),
Paint()
..color = Colors.transparent
..style = PaintingStyle.fill
..strokeWidth = 2,
);
}
}
final List<BoxShadow> _customShadow = [ final List<BoxShadow> _customShadow = [
BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white), BoxShadow(blurRadius: 26, offset: Offset(-6, -6), color: Colors.white),

View File

@ -15,6 +15,7 @@ const String updateIntervalKey = 'updateInterval';
const String downloadUsingDataKey = 'downloadUsingData'; const String downloadUsingDataKey = 'downloadUsingData';
const String introKey = 'intro'; const String introKey = 'intro';
const String realDarkKey = 'realDark'; const String realDarkKey = 'realDark';
const String cacheMaxKey = 'cacheMax';
class KeyValueStorage { class KeyValueStorage {
final String key; final String key;

View File

@ -264,12 +264,14 @@ class _SettingsState extends State<Settings>
), ),
Divider(height: 2), Divider(height: 2),
ListTile( ListTile(
onTap: () { onTap: () async {
if (_value == 0) if (_value == 0) {
_showFeedback = !_showFeedback;
_controller.forward(); _controller.forward();
else } else {
_controller.reverse(); await _controller.reverse();
_showFeedback = !_showFeedback; _showFeedback = !_showFeedback;
}
}, },
contentPadding: contentPadding:
EdgeInsets.symmetric(horizontal: 25.0), EdgeInsets.symmetric(horizontal: 25.0),

View File

@ -1,11 +1,57 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:app_settings/app_settings.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:tsacdop/settings/downloads_manage.dart'; import 'package:tsacdop/settings/downloads_manage.dart';
import 'package:tsacdop/class/settingstate.dart'; import 'package:tsacdop/class/settingstate.dart';
import '../local_storage/key_value_storage.dart';
import '../util/context_extension.dart';
class StorageSetting extends StatefulWidget {
@override
_StorageSettingState createState() => _StorageSettingState();
}
class _StorageSettingState extends State<StorageSetting>
with SingleTickerProviderStateMixin {
final KeyValueStorage cacheStorage = KeyValueStorage(cacheMaxKey);
AnimationController _controller;
Animation<double> _animation;
_getCacheMax() async {
int cache = await cacheStorage.getInt();
int value = cache == 0 ? 500 : cache ~/ (1024 * 1024);
if (value > 100) {
_controller = AnimationController(
vsync: this, duration: Duration(milliseconds: value * 2));
_animation = Tween<double>(begin: 100, end: value.toDouble()).animate(
CurvedAnimation(curve: Curves.easeOutQuart, parent: _controller))
..addListener(() {
setState(() => _value = _animation.value);
});
_controller.forward();
// _controller.addStatusListener((status) {
// if (status == AnimationStatus.completed) {
// _controller.reset();
// }
// });
}
}
double _value;
@override
void initState() {
super.initState();
_value = 100;
_getCacheMax();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
class StorageSetting extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var settings = Provider.of<SettingState>(context, listen: false); var settings = Provider.of<SettingState>(context, listen: false);
@ -54,8 +100,8 @@ class StorageSetting extends StatelessWidget {
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => DownloadsManage())), builder: (context) => DownloadsManage())),
contentPadding: contentPadding: EdgeInsets.only(
EdgeInsets.only(left: 80.0, right: 25, bottom: 10, top: 10), left: 80.0, right: 25, bottom: 10, top: 10),
title: Text('Ask before using cellular data'), title: Text('Ask before using cellular data'),
subtitle: Text( subtitle: Text(
'Ask to confirm when using cellular data to download episodes.'), 'Ask to confirm when using cellular data to download episodes.'),
@ -108,11 +154,44 @@ class StorageSetting extends StatelessWidget {
), ),
Divider(height: 2), Divider(height: 2),
ListTile( ListTile(
onTap: () => AppSettings.openAppSettings(), contentPadding: EdgeInsets.only(left: 80.0, right: 25),
contentPadding: EdgeInsets.symmetric(horizontal: 80.0),
// leading: Icon(Icons.colorize), // leading: Icon(Icons.colorize),
title: Text('Cache'), title: Text('Cache'),
subtitle: Text('App cache'), subtitle: Text('Audio cache max size'),
trailing: Text.rich(TextSpan(
text: '${(_value ~/ 100) * 100}',
style: GoogleFonts.teko(
textStyle: context.textTheme.headline6
.copyWith(color: context.accentColor)),
children: [
TextSpan(
text: ' Mb',
style: context.textTheme.subtitle2),
])),
),
Padding(
padding: EdgeInsets.only(
left: 60.0, right: 20.0, bottom: 10.0),
child: SliderTheme(
data: Theme.of(context).sliderTheme.copyWith(
showValueIndicator: ShowValueIndicator.always,
),
child: Slider(
label: '${_value ~/ 100 * 100} Mb',
activeColor: context.accentColor,
inactiveColor: context.primaryColorDark,
value: _value,
min: 100,
max: 1000,
divisions: 9,
onChanged: (double val) {
setState(() {
_value = val;
});
cacheStorage
.saveInt((val * 1024 * 1024).toInt());
}),
),
), ),
Divider(height: 2), Divider(height: 2),
], ],

View File

@ -123,7 +123,9 @@ class ThemeSetting extends StatelessWidget {
contentPadding: contentPadding:
EdgeInsets.only(left: 80.0, right: 20, bottom: 10), EdgeInsets.only(left: 80.0, right: 20, bottom: 10),
// leading: Icon(Icons.colorize), // leading: Icon(Icons.colorize),
title: Text('Real Dark',), title: Text(
'Real Dark',
),
subtitle: Text( subtitle: Text(
'Turn on if you think the night is not dark enough'), 'Turn on if you think the night is not dark enough'),
trailing: Selector<SettingState, bool>( trailing: Selector<SettingState, bool>(
@ -167,18 +169,22 @@ class ThemeSetting extends StatelessWidget {
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(10.0))), Radius.circular(10.0))),
title: Text('Choose a color'), title: Text('Choose a color'),
content: SingleChildScrollView( content: MaterialPicker(
child: MaterialPicker( onColorChanged: (value) {
onColorChanged: (value) { settings.setAccentColor = value;
settings.setAccentColor = value; },
}, pickerColor: context.accentColor,
pickerColor: context.accentColor,
),
), ),
))), ))),
contentPadding: EdgeInsets.symmetric(horizontal: 80.0), contentPadding: EdgeInsets.only(left: 80.0, right: 25),
title: Text('Accent color'), title: Text('Accent color'),
subtitle: Text('Include the overlay color'), subtitle: Text('Include the overlay color'),
trailing: Container(
height: 25,
width: 25,
decoration: BoxDecoration(
shape: BoxShape.circle, color: context.accentColor),
),
), ),
Divider(height: 2), Divider(height: 2),
], ],
@ -191,3 +197,13 @@ class ThemeSetting extends StatelessWidget {
); );
} }
} }
const List<Color> colorList = [
Colors.pink,
Colors.pinkAccent,
Colors.red,
Colors.blue,
Colors.green
];
List<int> intList = List<int>.generate(9, (index) => (index + 1) * 100);

View File

@ -0,0 +1,87 @@
import 'package:flutter/material.dart';
class MyRectangularTrackShape extends RectangularSliderTrackShape {
Rect getPreferredRect({
@required RenderBox parentBox,
Offset offset = Offset.zero,
@required SliderThemeData sliderTheme,
bool isEnabled = false,
bool isDiscrete = false,
}) {
final double trackHeight = sliderTheme.trackHeight;
final double trackLeft = offset.dx;
final double trackTop =
offset.dy + (parentBox.size.height - trackHeight) / 2;
final double trackWidth = parentBox.size.width;
return Rect.fromLTWH(trackLeft - 5, trackTop, trackWidth, trackHeight);
}
}
class MyRoundSliderThumpShape extends SliderComponentShape {
const MyRoundSliderThumpShape({
this.enabledThumbRadius = 10.0,
this.disabledThumbRadius,
this.thumbCenterColor,
});
final Color thumbCenterColor;
final double enabledThumbRadius;
final double disabledThumbRadius;
double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
@override
Size getPreferredSize(bool isEnabled, bool isDiscrete) {
return Size.fromRadius(
isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
}
@override
void paint(
PaintingContext context,
Offset center, {
Animation<double> activationAnimation,
@required Animation<double> enableAnimation,
bool isDiscrete,
TextPainter labelPainter,
RenderBox parentBox,
@required SliderThemeData sliderTheme,
TextDirection textDirection,
double value,
}) {
final Canvas canvas = context.canvas;
final Tween<double> radiusTween = Tween<double>(
begin: _disabledThumbRadius,
end: enabledThumbRadius,
);
// final ColorTween colorTween = ColorTween(
// begin: sliderTheme.disabledThumbColor,
// end: sliderTheme.thumbColor,
// );
canvas.drawCircle(
center,
radiusTween.evaluate(enableAnimation),
Paint()
..color = thumbCenterColor
..style = PaintingStyle.fill
..strokeWidth = 2,
);
canvas.drawRect(
Rect.fromLTRB(
center.dx - 10, center.dy + 10, center.dx + 10, center.dy - 10),
Paint()
..color = Colors.white
..style = PaintingStyle.fill
..strokeWidth = 10,
);
canvas.drawLine(
Offset(center.dx - 5, center.dy - 2),
Offset(center.dx + 5, center.dy + 2),
Paint()
..color = Colors.transparent
..style = PaintingStyle.fill
..strokeWidth = 2,
);
}
}

View File

@ -1,16 +1,6 @@
name: tsacdop name: tsacdop
description: An easy-use podacasts player. description: An easy-use podacasts player.
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.2.0+1 version: 0.2.0+1
environment: environment:
@ -20,8 +10,6 @@ dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.3 cupertino_icons: ^0.1.3
dev_dependencies: dev_dependencies:
@ -30,19 +18,19 @@ dev_dependencies:
json_annotation: ^3.0.1 json_annotation: ^3.0.1
sqflite: ^1.3.0 sqflite: ^1.3.0
flutter_html: ^0.11.1 flutter_html: ^0.11.1
path_provider: ^1.6.5 path_provider: ^1.6.7
color_thief_flutter: ^1.0.2 color_thief_flutter: ^1.0.2
provider: ^4.0.5 provider: ^4.0.5
google_fonts: ^1.0.0 google_fonts: ^1.0.0
dio: ^3.0.9 dio: ^3.0.9
file_picker: ^1.6.3+1 file_picker: ^1.6.3+1
xml: ^3.5.0 xml: ^3.6.1
marquee: ^1.3.1 marquee: ^1.3.1
flutter_downloader: ^1.4.3 flutter_downloader: ^1.4.4
permission_handler: ^5.0.0+hotfix.3 permission_handler: ^5.0.0+hotfix.3
fluttertoast: ^4.0.1 fluttertoast: ^4.0.1
intl: ^0.16.1 intl: ^0.16.1
url_launcher: ^5.4.2 url_launcher: ^5.4.5
image: ^2.1.12 image: ^2.1.12
shared_preferences: ^0.5.7 shared_preferences: ^0.5.7
uuid: ^2.0.4 uuid: ^2.0.4
@ -50,7 +38,6 @@ dev_dependencies:
cached_network_image: ^2.1.0+1 cached_network_image: ^2.1.0+1
workmanager: ^0.2.2 workmanager: ^0.2.2
flutter_colorpicker: ^0.3.4 flutter_colorpicker: ^0.3.4
app_settings: ^3.0.1
fl_chart: ^0.9.3 fl_chart: ^0.9.3
audio_service: ^0.7.2 audio_service: ^0.7.2
just_audio: just_audio:
@ -60,7 +47,7 @@ dev_dependencies:
git: git:
url: https://github.com/galonsos/line_icons.git url: https://github.com/galonsos/line_icons.git
flutter_file_dialog: ^0.0.5 flutter_file_dialog: ^0.0.5
flutter_linkify: ^3.1.0 flutter_linkify: ^3.1.2
extended_nested_scroll_view: ^0.4.0 extended_nested_scroll_view: ^0.4.0
connectivity: ^0.4.8+2 connectivity: ^0.4.8+2
flare_flutter: ^2.0.3 flare_flutter: ^2.0.3
@ -68,10 +55,6 @@ dev_dependencies:
flutter_isolate: ^1.0.0+11 flutter_isolate: ^1.0.0+11
# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec
# The following section is specific to Flutter.
flutter: flutter:
assets: assets:
- assets/ - assets/