Search ui changed a lot, add podcast detail panel.

Update audio service to latest version.
This commit is contained in:
stonegate 2020-07-22 17:34:32 +08:00
parent 602cc67342
commit b619be9a9b
49 changed files with 1493 additions and 772 deletions

View File

@ -1,13 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.stonegate.tsacdop" <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.stonegate.tsacdop" xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that <!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method. calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. --> FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:name="io.flutter.app.FlutterApplication" android:label="Tsacdop" android:icon="@mipmap/ic_launcher_icon" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config"> <application android:name="io.flutter.app.FlutterApplication" android:label="Tsacdop" android:icon="@mipmap/ic_launcher_icon" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize"> <activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
<meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/LaunchTheme" /> <meta-data android:name="io.flutter.embedding.android.NormalTheme" android:resource="@style/LaunchTheme" />
@ -22,11 +21,8 @@
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service> </service>
<receiver android:name="androidx.media.session.MediaButtonReceiver"> <receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" /> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>

View File

@ -14,8 +14,9 @@ import 'package:google_fonts/google_fonts.dart';
import '../state/audio_state.dart'; import '../state/audio_state.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../type/play_histroy.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/custompaint.dart'; import '../util/custompaint.dart';
import 'episode_download.dart'; import 'episode_download.dart';

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';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
const String version = '0.4.7'; const String version = '0.4.7';

View File

@ -9,17 +9,18 @@ import 'package:audio_service/audio_service.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../type/play_histroy.dart';
import '../state/audio_state.dart'; import '../state/audio_state.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../util/pageroute.dart'; import '../util/pageroute.dart';
import '../util/colorize.dart'; import '../util/colorize.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/custompaint.dart'; import '../util/custompaint.dart';
import '../util/custom_slider.dart'; import '../util/custom_slider.dart';
import '../episodes/episode_detail.dart'; import '../episodes/episode_detail.dart';
import 'playlist.dart'; import 'playlist.dart';
import 'audiopanel.dart'; import '../util/audiopanel.dart';
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),
@ -438,9 +439,9 @@ class _PlayerWidgetState extends State<PlayerWidget> {
Expanded( Expanded(
flex: 2, flex: 2,
child: Selector<AudioPlayerNotifier, child: Selector<AudioPlayerNotifier,
Tuple3<BasicPlaybackState, double, String>>( Tuple3<bool, double, String>>(
selector: (_, audio) => Tuple3( selector: (_, audio) => Tuple3(
audio.audioState, audio.buffering,
(audio.backgroundAudioDuration - (audio.backgroundAudioDuration -
audio.backgroundAudioPosition) / audio.backgroundAudioPosition) /
1000, 1000,
@ -453,12 +454,7 @@ class _PlayerWidgetState extends State<PlayerWidget> {
? Text(data.item3, ? Text(data.item3,
style: const TextStyle( style: const TextStyle(
color: const Color(0xFFFF0000))) color: const Color(0xFFFF0000)))
: data.item1 == BasicPlaybackState.buffering || : data.item1
data.item1 ==
BasicPlaybackState.connecting ||
data.item1 ==
BasicPlaybackState.skippingToNext ||
data.item1 == BasicPlaybackState.stopped
? Text( ? Text(
s.buffering, s.buffering,
style: TextStyle( style: TextStyle(
@ -475,35 +471,18 @@ class _PlayerWidgetState extends State<PlayerWidget> {
), ),
Expanded( Expanded(
flex: 2, flex: 2,
child: Selector<AudioPlayerNotifier, BasicPlaybackState>( child: Selector<AudioPlayerNotifier, Tuple2<bool, bool>>(
selector: (_, audio) => audio.audioState, selector: (_, audio) =>
builder: (_, audioplay, __) { Tuple2(audio.buffering, audio.playing),
builder: (_, data, __) {
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
//Spacer(), //Spacer(),
audioplay == BasicPlaybackState.playing data.item1
? InkWell( ? Stack(
onTap: alignment: Alignment.center,
audioplay == BasicPlaybackState.playing children: <Widget>[
? () {
audio.pauseAduio();
}
: null,
child: ImageRotate(
title: audio.episode?.title,
path: audio.episode?.imagePath),
)
: InkWell(
onTap:
audioplay == BasicPlaybackState.playing
? null
: () {
audio.resumeAudio();
},
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container( Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
vertical: 10.0), vertical: 10.0),
@ -521,13 +500,48 @@ class _PlayerWidgetState extends State<PlayerWidget> {
shape: BoxShape.circle, shape: BoxShape.circle,
color: Colors.black), color: Colors.black),
), ),
Icon( ])
Icons.play_arrow, : data.item2
color: Colors.white, ? InkWell(
) onTap: data.item2
], ? () => audio.pauseAduio()
), : null,
), child: ImageRotate(
title: audio.episode?.title,
path: audio.episode?.imagePath),
)
: InkWell(
onTap: data.item2
? null
: () => audio.resumeAudio(),
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(
vertical: 10.0),
child: Container(
height: 30.0,
width: 30.0,
child: CircleAvatar(
backgroundImage: FileImage(File(
"${audio.episode.imagePath}")),
)),
),
Container(
height: 40.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black),
),
if (!data.item1)
Icon(
Icons.play_arrow,
color: Colors.white,
)
],
),
),
IconButton( IconButton(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
onPressed: () => audio.playNext(), onPressed: () => audio.playNext(),
@ -1148,7 +1162,7 @@ class _ControlPanelState extends State<ControlPanel>
Widget build(BuildContext context) { Widget build(BuildContext context) {
var audio = Provider.of<AudioPlayerNotifier>(context, listen: false); var audio = Provider.of<AudioPlayerNotifier>(context, listen: false);
return Container( return Container(
color: Theme.of(context).primaryColor, color: context.primaryColor,
height: 300, height: 300,
padding: EdgeInsets.symmetric(horizontal: 10.0), padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Stack( child: Stack(
@ -1159,10 +1173,9 @@ class _ControlPanelState extends State<ControlPanel>
children: <Widget>[ children: <Widget>[
Consumer<AudioPlayerNotifier>( Consumer<AudioPlayerNotifier>(
builder: (_, data, __) { builder: (_, data, __) {
Color _c = Color _c = (context.brightness == Brightness.light)
(Theme.of(context).brightness == Brightness.light) ? data.episode.primaryColor.colorizedark()
? data.episode.primaryColor.colorizedark() : data.episode.primaryColor.colorizeLight();
: data.episode.primaryColor.colorizeLight();
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -1217,15 +1230,16 @@ class _ControlPanelState extends State<ControlPanel>
color: const Color(0xFFFF0000))) color: const Color(0xFFFF0000)))
: Text( : Text(
data.audioState == data.audioState ==
BasicPlaybackState AudioProcessingState
.buffering || .buffering ||
data.audioState == data.audioState ==
BasicPlaybackState AudioProcessingState
.connecting || .connecting ||
data.audioState == data.audioState ==
BasicPlaybackState.none || AudioProcessingState
.none ||
data.audioState == data.audioState ==
BasicPlaybackState AudioProcessingState
.skippingToNext .skippingToNext
? context.s.buffering ? context.s.buffering
: '', : '',
@ -1250,9 +1264,9 @@ class _ControlPanelState extends State<ControlPanel>
), ),
Container( Container(
height: 100, height: 100,
child: Selector<AudioPlayerNotifier, BasicPlaybackState>( child: Selector<AudioPlayerNotifier, bool>(
selector: (_, audio) => audio.audioState, selector: (_, audio) => audio.playing,
builder: (_, backplay, __) { builder: (_, playing, __) {
return Material( return Material(
color: Colors.transparent, color: Colors.transparent,
child: Row( child: Row(
@ -1261,10 +1275,9 @@ class _ControlPanelState extends State<ControlPanel>
children: [ children: [
IconButton( IconButton(
padding: EdgeInsets.symmetric(horizontal: 25.0), padding: EdgeInsets.symmetric(horizontal: 25.0),
onPressed: onPressed: playing
backplay == BasicPlaybackState.playing ? () => audio.forwardAudio(-10)
? () => audio.forwardAudio(-10) : null,
: null,
iconSize: 32.0, iconSize: 32.0,
icon: Icon(Icons.replay_10), icon: Icon(Icons.replay_10),
color: Colors.grey[500]), color: Colors.grey[500]),
@ -1285,14 +1298,13 @@ class _ControlPanelState extends State<ControlPanel>
Brightness.dark Brightness.dark
? _customShadowNight ? _customShadowNight
: _customShadow), : _customShadow),
child: backplay == BasicPlaybackState.playing child: playing
? Material( ? Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(30)), Radius.circular(30)),
onTap: backplay == onTap: playing
BasicPlaybackState.playing
? () { ? () {
audio.pauseAduio(); audio.pauseAduio();
} }
@ -1312,8 +1324,7 @@ class _ControlPanelState extends State<ControlPanel>
child: InkWell( child: InkWell(
borderRadius: BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(30)), Radius.circular(30)),
onTap: backplay == onTap: playing
BasicPlaybackState.playing
? null ? null
: () { : () {
audio.resumeAudio(); audio.resumeAudio();
@ -1333,10 +1344,9 @@ class _ControlPanelState extends State<ControlPanel>
), ),
IconButton( IconButton(
padding: EdgeInsets.symmetric(horizontal: 25.0), padding: EdgeInsets.symmetric(horizontal: 25.0),
onPressed: onPressed: playing
backplay == BasicPlaybackState.playing ? () => audio.forwardAudio(30)
? () => audio.forwardAudio(30) : null,
: null,
iconSize: 32.0, iconSize: 32.0,
icon: Icon(Icons.forward_30), icon: Icon(Icons.forward_30),
color: Colors.grey[500]), color: Colors.grey[500]),

View File

@ -11,12 +11,13 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:feature_discovery/feature_discovery.dart'; import 'package:feature_discovery/feature_discovery.dart';
import '../state/audio_state.dart'; import '../state/audio_state.dart';
import '../type/playlist.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../util/episodegrid.dart'; import '../util/episodegrid.dart';
import '../util/mypopupmenu.dart'; import '../util/mypopupmenu.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/custompaint.dart'; import '../util/custompaint.dart';
import '../state/download_state.dart'; import '../state/download_state.dart';
import '../state/podcast_group.dart'; import '../state/podcast_group.dart';

View File

@ -12,6 +12,7 @@ import 'package:tuple/tuple.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../type/play_histroy.dart';
import '../state/podcast_group.dart'; import '../state/podcast_group.dart';
import '../state/download_state.dart'; import '../state/download_state.dart';
import '../type/podcastlocal.dart'; import '../type/podcastlocal.dart';
@ -19,7 +20,7 @@ import '../state/audio_state.dart';
import '../util/custompaint.dart'; import '../util/custompaint.dart';
import '../util/pageroute.dart'; import '../util/pageroute.dart';
import '../util/colorize.dart'; import '../util/colorize.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../episodes/episode_detail.dart'; import '../episodes/episode_detail.dart';

View File

@ -15,7 +15,7 @@ import 'package:intl/intl.dart';
import '../settings/settting.dart'; import '../settings/settting.dart';
import '../state/refresh_podcast.dart'; import '../state/refresh_podcast.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import 'about.dart'; import 'about.dart';
class PopupMenu extends StatefulWidget { class PopupMenu extends StatefulWidget {

View File

@ -8,7 +8,7 @@ import '../state/podcast_group.dart';
import '../state/download_state.dart'; import '../state/download_state.dart';
import '../state/refresh_podcast.dart'; import '../state/refresh_podcast.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
class Import extends StatelessWidget { class Import extends StatelessWidget {
Widget importColumn(String text, BuildContext context) { Widget importColumn(String text, BuildContext context) {

View File

@ -10,7 +10,8 @@ import 'package:line_icons/line_icons.dart';
import '../state/audio_state.dart'; import '../state/audio_state.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../util/context_extension.dart'; import '../type/playlist.dart';
import '../util/extension_helper.dart';
import '../util/custompaint.dart'; import '../util/custompaint.dart';
import '../util/colorize.dart'; import '../util/colorize.dart';

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
import '../state/setting_state.dart'; import '../state/setting_state.dart';
import '../home/home.dart'; import '../home/home.dart';
import '../util/pageroute.dart'; import '../util/pageroute.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import 'fourthpage.dart'; import 'fourthpage.dart';
import 'secondpage.dart'; import 'secondpage.dart';
import 'thirdpage.dart'; import 'thirdpage.dart';

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
class FirstPage extends StatefulWidget { class FirstPage extends StatefulWidget {
FirstPage({Key key}) : super(key: key); FirstPage({Key key}) : super(key: key);

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
class FourthPage extends StatefulWidget { class FourthPage extends StatefulWidget {
FourthPage({Key key}) : super(key: key); FourthPage({Key key}) : super(key: key);

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
class SecondPage extends StatefulWidget { class SecondPage extends StatefulWidget {
SecondPage({Key key}) : super(key: key); SecondPage({Key key}) : super(key: key);

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
class ThirdPage extends StatefulWidget { class ThirdPage extends StatefulWidget {
ThirdPage({Key key}) : super(key: key); ThirdPage({Key key}) : super(key: key);

View File

@ -25,13 +25,14 @@ const String downloadLayoutKey = 'downloadLayoutKey';
const String autoDownloadNetworkKey = 'autoDownloadNetwork'; const String autoDownloadNetworkKey = 'autoDownloadNetwork';
const String episodePopupMenuKey = 'episodePopupMenuKey'; const String episodePopupMenuKey = 'episodePopupMenuKey';
const String autoDeleteKey = 'autoDeleteKey'; const String autoDeleteKey = 'autoDeleteKey';
//SleepTImer
const String autoSleepTimerKey = 'autoSleepTimerKey'; const String autoSleepTimerKey = 'autoSleepTimerKey';
const String autoSleepTimerStartKey = 'autoSleepTimerStartKey'; const String autoSleepTimerStartKey = 'autoSleepTimerStartKey';
const String autoSleepTimerEndKey = 'autoSleepTimerEndKey'; const String autoSleepTimerEndKey = 'autoSleepTimerEndKey';
const String defaultSleepTimerKey = 'defaultSleepTimerKey'; const String defaultSleepTimerKey = 'defaultSleepTimerKey';
const String autoSleepTimerModeKey = 'autoSleepTimerModeKey'; const String autoSleepTimerModeKey = 'autoSleepTimerModeKey';
const String tapToOpenPopupMenuKey = 'tapToOpenPopupMenuKey'; const String tapToOpenPopupMenuKey = 'tapToOpenPopupMenuKey';
const String fastForwardSecondsKey = 'fastForwardSecondsKey';
const String rewindSecondsKey = 'rewindSecondsKey';
class KeyValueStorage { class KeyValueStorage {
final String key; final String key;

View File

@ -5,7 +5,7 @@ import 'package:intl/intl.dart';
import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import '../type/podcastlocal.dart'; import '../type/podcastlocal.dart';
import '../state/audio_state.dart'; import '../type/play_histroy.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../webfeed/webfeed.dart'; import '../webfeed/webfeed.dart';
import '../type/sub_history.dart'; import '../type/sub_history.dart';

View File

@ -16,13 +16,14 @@ import 'package:cached_network_image/cached_network_image.dart';
import '../type/podcastlocal.dart'; import '../type/podcastlocal.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../type/play_histroy.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../util/episodegrid.dart'; import '../util/episodegrid.dart';
import '../home/audioplayer.dart'; import '../home/audioplayer.dart';
import '../type/fireside_data.dart'; import '../type/fireside_data.dart';
import '../util/colorize.dart'; import '../util/colorize.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/custompaint.dart'; import '../util/custompaint.dart';
import '../util/general_dialog.dart'; import '../util/general_dialog.dart';
import '../state/audio_state.dart'; import '../state/audio_state.dart';

View File

@ -13,7 +13,7 @@ import '../type/podcastlocal.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../util/colorize.dart'; import '../util/colorize.dart';
import '../util/duraiton_picker.dart'; import '../util/duraiton_picker.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/general_dialog.dart'; import '../util/general_dialog.dart';
class PodcastGroupList extends StatefulWidget { class PodcastGroupList extends StatefulWidget {

View File

@ -12,7 +12,7 @@ import '../state/podcast_group.dart';
import 'podcast_group.dart'; import 'podcast_group.dart';
import 'podcastlist.dart'; import 'podcastlist.dart';
import '../util/pageroute.dart'; import '../util/pageroute.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/general_dialog.dart'; import '../util/general_dialog.dart';
import 'custom_tabview.dart'; import 'custom_tabview.dart';

View File

@ -13,7 +13,7 @@ import '../type/podcastlocal.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import 'podcast_detail.dart'; import 'podcast_detail.dart';
import '../util/pageroute.dart'; import '../util/pageroute.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
class AboutPodcast extends StatefulWidget { class AboutPodcast extends StatefulWidget {
final PodcastLocal podcastLocal; final PodcastLocal podcastLocal;

View File

@ -0,0 +1,39 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import '../type/searchpodcast.dart';
import '../type/searchepisodes.dart';
import '../.env.dart';
class SearchEngine {
searchPodcasts({String searchText, int nextOffset}) async {
String apiKey = environment['apiKey'];
String url = "https://listen-api.listennotes.com/api/v2/search?q=" +
Uri.encodeComponent(searchText) +
"&sort_by_date=0&type=podcast&offset=$nextOffset";
Response response = await Dio().get(url,
options: Options(headers: {
'X-ListenAPI-Key': "$apiKey",
'Accept': "application/json"
}));
Map searchResultMap = jsonDecode(response.toString());
var searchResult = SearchPodcast.fromJson(searchResultMap);
return searchResult;
}
Future<SearchEpisodes<dynamic>> fetchEpisode(
{String id, int nextEpisodeDate}) async {
String apiKey = environment['apiKey'];
String url =
"https://listen-api.listennotes.com/api/v2/podcasts/$id?next_episode_pub_date=$nextEpisodeDate";
Response response = await Dio().get(url,
options: Options(headers: {
'X-ListenAPI-Key': "$apiKey",
'Accept': "application/json"
}));
Map searchResultMap = jsonDecode(response.toString());
var searchResult = SearchEpisodes.fromJson(searchResultMap);
return searchResult;
}
}

View File

@ -16,7 +16,7 @@ import 'package:wc_flutter_share/wc_flutter_share.dart';
import '../state/podcast_group.dart'; import '../state/podcast_group.dart';
import '../state/setting_state.dart'; import '../state/setting_state.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../service/ompl_build.dart'; import '../service/ompl_build.dart';
class DataBackup extends StatefulWidget { class DataBackup extends StatefulWidget {

View File

@ -9,7 +9,7 @@ import 'package:provider/provider.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../state/download_state.dart'; import '../state/download_state.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';

View File

@ -11,8 +11,8 @@ import 'package:tsacdop/state/podcast_group.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../webfeed/webfeed.dart'; import '../webfeed/webfeed.dart';
import '../type/searchpodcast.dart'; import '../type/searchpodcast.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../state/audio_state.dart'; import '../type/play_histroy.dart';
import '../state/podcast_group.dart'; import '../state/podcast_group.dart';
import '../type/sub_history.dart'; import '../type/sub_history.dart';

View File

@ -4,7 +4,7 @@ import 'package:intl/intl.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../generated/l10n.dart'; import '../generated/l10n.dart';
class LanguagesSetting extends StatefulWidget { class LanguagesSetting extends StatefulWidget {

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/episodegrid.dart'; import '../util/episodegrid.dart';
import '../util/custompaint.dart'; import '../util/custompaint.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import 'licenses.dart'; import 'licenses.dart';
class Libries extends StatelessWidget { class Libries extends StatelessWidget {

View File

@ -11,7 +11,7 @@ import 'package:flutter_time_picker_spinner/flutter_time_picker_spinner.dart';
import '../state/setting_state.dart'; import '../state/setting_state.dart';
import '../home/audioplayer.dart'; import '../home/audioplayer.dart';
import '../util/general_dialog.dart'; import '../util/general_dialog.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/custom_dropdown.dart'; import '../util/custom_dropdown.dart';
String stringForMins(int mins) { String stringForMins(int mins) {

View File

@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
import 'package:line_icons/line_icons.dart'; import 'package:line_icons/line_icons.dart';
import 'package:flare_flutter/flare_actor.dart'; import 'package:flare_flutter/flare_actor.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/custompaint.dart'; import '../util/custompaint.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';

View File

@ -7,7 +7,7 @@ import 'package:url_launcher/url_launcher.dart';
import 'package:feature_discovery/feature_discovery.dart'; import 'package:feature_discovery/feature_discovery.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../intro_slider/app_intro.dart'; import '../intro_slider/app_intro.dart';
import '../home/home.dart'; import '../home/home.dart';
import '../podcasts/podcast_manage.dart'; import '../podcasts/podcast_manage.dart';

View File

@ -6,7 +6,7 @@ import 'package:google_fonts/google_fonts.dart';
import '../settings/downloads_manage.dart'; import '../settings/downloads_manage.dart';
import '../state/setting_state.dart'; import '../state/setting_state.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/custom_dropdown.dart'; import '../util/custom_dropdown.dart';
class StorageSetting extends StatefulWidget { class StorageSetting extends StatefulWidget {

View File

@ -4,7 +4,7 @@ import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../state/setting_state.dart'; import '../state/setting_state.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/custom_dropdown.dart'; import '../util/custom_dropdown.dart';
class SyncingSetting extends StatelessWidget { class SyncingSetting extends StatelessWidget {

View File

@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../state/setting_state.dart'; import '../state/setting_state.dart';
import '../util/context_extension.dart'; import '../util/extension_helper.dart';
import '../util/general_dialog.dart'; import '../util/general_dialog.dart';
class ThemeSetting extends StatelessWidget { class ThemeSetting extends StatelessWidget {

View File

@ -1,19 +1,15 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:math' as math;
import 'package:path/path.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:rxdart/rxdart.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../type/play_histroy.dart';
import '../type/playlist.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../.env.dart';
MediaControl playControl = MediaControl( MediaControl playControl = MediaControl(
androidIcon: 'drawable/ic_stat_play_circle_filled', androidIcon: 'drawable/ic_stat_play_circle_filled',
@ -50,144 +46,113 @@ void _audioPlayerTaskEntrypoint() async {
AudioServiceBackground.run(() => AudioPlayerTask()); AudioServiceBackground.run(() => AudioPlayerTask());
} }
class PlayHistory { /// Sleep timer mode.
DBHelper dbHelper = DBHelper();
String title;
String url;
double seconds;
double seekValue;
DateTime playdate;
PlayHistory(this.title, this.url, this.seconds, this.seekValue,
{this.playdate});
EpisodeBrief _episode;
EpisodeBrief get episode => _episode;
getEpisode() async {
_episode = await dbHelper.getRssItemWithUrl(url);
}
}
class Playlist {
String name;
DBHelper dbHelper = DBHelper();
List<EpisodeBrief> _playlist;
List<EpisodeBrief> get playlist => _playlist;
KeyValueStorage storage = KeyValueStorage('playlist');
getPlaylist() async {
List<String> urls = await storage.getStringList();
if (urls.length == 0) {
_playlist = [];
} else {
_playlist = [];
for (String url in urls) {
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
if (episode != null) _playlist.add(episode);
}
}
}
savePlaylist() async {
List<String> urls = [];
urls.addAll(_playlist.map((e) => e.enclosureUrl));
await storage.saveStringList(urls.toSet().toList());
}
addToPlayList(EpisodeBrief episodeBrief) async {
if (!_playlist.contains(episodeBrief)) {
_playlist.add(episodeBrief);
await savePlaylist();
dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl);
}
}
addToPlayListAt(EpisodeBrief episodeBrief, int index) async {
if (!_playlist.contains(episodeBrief)) {
_playlist.insert(index, episodeBrief);
await savePlaylist();
dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl);
}
}
Future<int> delFromPlaylist(EpisodeBrief episodeBrief) async {
int index = _playlist.indexOf(episodeBrief);
_playlist.removeWhere(
(episode) => episode.enclosureUrl == episodeBrief.enclosureUrl);
print('delete' + episodeBrief.title);
await savePlaylist();
return index;
}
}
enum SleepTimerMode { endOfEpisode, timer, undefined } enum SleepTimerMode { endOfEpisode, timer, undefined }
enum ShareStatus { generate, download, complete, undefined, error }
//enum ShareStatus { generate, download, complete, undefined, error }
class AudioPlayerNotifier extends ChangeNotifier { class AudioPlayerNotifier extends ChangeNotifier {
DBHelper dbHelper = DBHelper(); DBHelper dbHelper = DBHelper();
KeyValueStorage positionStorage = KeyValueStorage(audioPositionKey); var positionStorage = KeyValueStorage(audioPositionKey);
KeyValueStorage autoPlayStorage = KeyValueStorage(autoPlayKey); var autoPlayStorage = KeyValueStorage(autoPlayKey);
KeyValueStorage autoSleepTimerStorage = KeyValueStorage(autoSleepTimerKey); var autoSleepTimerStorage = KeyValueStorage(autoSleepTimerKey);
KeyValueStorage defaultSleepTimerStorage = var defaultSleepTimerStorage = KeyValueStorage(defaultSleepTimerKey);
KeyValueStorage(defaultSleepTimerKey); var autoSleepTimerModeStorage = KeyValueStorage(autoSleepTimerModeKey);
KeyValueStorage autoSleepTimerModeStorage = var autoSleepTimerStartStorage = KeyValueStorage(autoSleepTimerStartKey);
KeyValueStorage(autoSleepTimerModeKey); var autoSleepTimerEndStorage = KeyValueStorage(autoSleepTimerEndKey);
KeyValueStorage autoSleepTimerStartStorage = var fastForwardSecondsStorage = KeyValueStorage(fastForwardSecondsKey);
KeyValueStorage(autoSleepTimerStartKey); var rewindSecondsStorage = KeyValueStorage(rewindSecondsKey);
KeyValueStorage autoSleepTimerEndStorage =
KeyValueStorage(autoSleepTimerEndKey);
/// Current playing episdoe.
EpisodeBrief _episode; EpisodeBrief _episode;
/// Current playlist.
Playlist _queue = Playlist(); Playlist _queue = Playlist();
/// Notifier for playlist change.
bool _queueUpdate = false; bool _queueUpdate = false;
BasicPlaybackState _audioState = BasicPlaybackState.none;
bool _playerRunning = false; /// Player state.
AudioProcessingState _audioState = AudioProcessingState.none;
/// Player playing.
bool _playing = false;
/// Fastforward second.
int _fastForwardSeconds;
/// Rewind seconds.
int _rewindSeconds;
/// No slide, set true if slide on seekbar.
bool _noSlide = true; bool _noSlide = true;
/// Current episode duration.
int _backgroundAudioDuration = 0; int _backgroundAudioDuration = 0;
/// Current episode positin.
int _backgroundAudioPosition = 0; int _backgroundAudioPosition = 0;
/// Erroe maeesage.
String _remoteErrorMessage; String _remoteErrorMessage;
/// Seekbar value, min 0, max 1.0.
double _seekSliderValue = 0.0; double _seekSliderValue = 0.0;
/// Record plyaer position.
int _lastPostion = 0; int _lastPostion = 0;
/// Set true if sleep timer mode is end of episode.
bool _stopOnComplete = false; bool _stopOnComplete = false;
/// Sleep timer timer.
Timer _stopTimer; Timer _stopTimer;
/// Sleep timer time left.
int _timeLeft = 0; int _timeLeft = 0;
/// Start sleep timer.
bool _startSleepTimer = false; bool _startSleepTimer = false;
/// Control sleep timer anamation.
double _switchValue = 0; double _switchValue = 0;
/// Sleep timer mode.
SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined; SleepTimerMode _sleepTimerMode = SleepTimerMode.undefined;
//Auto stop at the end of episode when you start play at scheduled time. //Auto stop at the end of episode when you start play at scheduled time.
bool _autoSleepTimer; bool _autoSleepTimer;
//Default sleep timer time.
ShareStatus _shareStatus = ShareStatus.undefined;
String _shareFile = '';
//set autoplay episode in playlist //set autoplay episode in playlist
bool _autoPlay; bool _autoPlay;
/// Datetime now.
DateTime _current; DateTime _current;
/// Current position.
int _currentPosition; int _currentPosition;
/// Current speed.
double _currentSpeed = 1; double _currentSpeed = 1;
BehaviorSubject<List<MediaItem>> queueSubject;
//Update episode card when setting changed //Update episode card when setting changed
bool _episodeState = false; bool _episodeState = false;
BasicPlaybackState get audioState => _audioState; AudioProcessingState get audioState => _audioState;
int get backgroundAudioDuration => _backgroundAudioDuration; int get backgroundAudioDuration => _backgroundAudioDuration;
int get backgroundAudioPosition => _backgroundAudioPosition; int get backgroundAudioPosition => _backgroundAudioPosition;
double get seekSliderValue => _seekSliderValue; double get seekSliderValue => _seekSliderValue;
String get remoteErrorMessage => _remoteErrorMessage; String get remoteErrorMessage => _remoteErrorMessage;
bool get playerRunning => _playerRunning; bool get playerRunning => _audioState != AudioProcessingState.none;
bool get buffering => _audioState != AudioProcessingState.ready;
int get lastPositin => _lastPostion; int get lastPositin => _lastPostion;
Playlist get queue => _queue; Playlist get queue => _queue;
bool get playing => _playing;
bool get queueUpdate => _queueUpdate; bool get queueUpdate => _queueUpdate;
EpisodeBrief get episode => _episode; EpisodeBrief get episode => _episode;
bool get stopOnComplete => _stopOnComplete; bool get stopOnComplete => _stopOnComplete;
bool get startSleepTimer => _startSleepTimer; bool get startSleepTimer => _startSleepTimer;
SleepTimerMode get sleepTimerMode => _sleepTimerMode; SleepTimerMode get sleepTimerMode => _sleepTimerMode;
ShareStatus get shareStatus => _shareStatus;
String get shareFile => _shareFile;
//bool get autoPlay => _autoPlay;
int get timeLeft => _timeLeft; int get timeLeft => _timeLeft;
double get switchValue => _switchValue; double get switchValue => _switchValue;
double get currentSpeed => _currentSpeed; double get currentSpeed => _currentSpeed;
@ -199,11 +164,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
set setShareStatue(ShareStatus status) {
_shareStatus = status;
notifyListeners();
}
set setEpisodeState(bool boo) { set setEpisodeState(bool boo) {
_episodeState = !_episodeState; _episodeState = !_episodeState;
notifyListeners(); notifyListeners();
@ -234,11 +194,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
if (running) {} if (running) {}
} }
loadPlaylist() async { Future<void> loadPlaylist() async {
await _queue.getPlaylist(); await _queue.getPlaylist();
await _getAutoPlay(); await _getAutoPlay();
// await _getAutoAdd();
// await addNewEpisode('all');
_lastPostion = await positionStorage.getInt(); _lastPostion = await positionStorage.getInt();
if (_lastPostion > 0 && _queue.playlist.length > 0) { if (_lastPostion > 0 && _queue.playlist.length > 0) {
final EpisodeBrief episode = _queue.playlist.first; final EpisodeBrief episode = _queue.playlist.first;
@ -252,12 +210,13 @@ class AudioPlayerNotifier extends ChangeNotifier {
await lastWorkStorage.saveInt(0); await lastWorkStorage.saveInt(0);
} }
episodeLoad(EpisodeBrief episode, {int startPosition = 0}) async { Future<void> episodeLoad(EpisodeBrief episode,
{int startPosition = 0}) async {
print(episode.enclosureUrl); print(episode.enclosureUrl);
final EpisodeBrief episodeNew = final EpisodeBrief episodeNew =
await dbHelper.getRssItemWithUrl(episode.enclosureUrl); 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 (playerRunning) {
PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl, PlayHistory history = PlayHistory(_episode.title, _episode.enclosureUrl,
backgroundAudioPosition / 1000, seekSliderValue); backgroundAudioPosition / 1000, seekSliderValue);
await dbHelper.saveHistory(history); await dbHelper.saveHistory(history);
@ -275,8 +234,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
_backgroundAudioPosition = 0; _backgroundAudioPosition = 0;
_seekSliderValue = 0; _seekSliderValue = 0;
_episode = episodeNew; _episode = episodeNew;
_playerRunning = true; _audioState = AudioProcessingState.connecting;
_audioState = BasicPlaybackState.connecting;
notifyListeners(); notifyListeners();
//await _queue.savePlaylist(); //await _queue.savePlaylist();
_startAudioService(startPosition, episodeNew.enclosureUrl); _startAudioService(startPosition, episodeNew.enclosureUrl);
@ -289,18 +247,29 @@ class AudioPlayerNotifier extends ChangeNotifier {
_startAudioService(int position, String url) async { _startAudioService(int position, String url) async {
_stopOnComplete = false; _stopOnComplete = false;
_sleepTimerMode = SleepTimerMode.undefined; _sleepTimerMode = SleepTimerMode.undefined;
/// Connect to audio service.
if (!AudioService.connected) { if (!AudioService.connected) {
await AudioService.connect(); await AudioService.connect();
} }
/// Get fastword and rewind seconds.
_fastForwardSeconds =
await fastForwardSecondsStorage.getInt(defaultValue: 30);
_rewindSeconds = await rewindSecondsStorage.getInt(defaultValue: 10);
/// Start audio service.
await AudioService.start( await AudioService.start(
backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint, backgroundTaskEntrypoint: _audioPlayerTaskEntrypoint,
androidNotificationChannelName: 'Tsacdop', androidNotificationChannelName: 'Tsacdop',
notificationColor: 0xFF4d91be, androidNotificationColor: 0xFF4d91be,
androidNotificationIcon: 'drawable/ic_notification', androidNotificationIcon: 'drawable/ic_notification',
enableQueue: true, androidEnableQueue: true,
androidStopOnRemoveTask: true, androidStopForegroundOnPause: true,
androidStopForegroundOnPause: true); fastForwardInterval: Duration(seconds: _fastForwardSeconds),
//Check autoplay setting rewindInterval: Duration(seconds: _rewindSeconds));
//Check autoplay setting, if true only add one episode, else add playlist.
await _getAutoPlay(); await _getAutoPlay();
if (_autoPlay) { if (_autoPlay) {
for (var episode in _queue.playlist) for (var episode in _queue.playlist)
@ -315,7 +284,6 @@ class AudioPlayerNotifier extends ChangeNotifier {
await autoSleepTimerStartStorage.getInt(defaultValue: 1380); await autoSleepTimerStartStorage.getInt(defaultValue: 1380);
int endTime = await autoSleepTimerEndStorage.getInt(defaultValue: 360); int endTime = await autoSleepTimerEndStorage.getInt(defaultValue: 360);
int currentTime = DateTime.now().hour * 60 + DateTime.now().minute; int currentTime = DateTime.now().hour * 60 + DateTime.now().minute;
print('CurrentTime' + currentTime.toString());
if ((startTime > endTime && if ((startTime > endTime &&
(currentTime > startTime || currentTime < endTime)) || (currentTime > startTime || currentTime < endTime)) ||
((startTime < endTime) && ((startTime < endTime) &&
@ -327,78 +295,106 @@ class AudioPlayerNotifier extends ChangeNotifier {
sleepTimer(defaultTimer); sleepTimer(defaultTimer);
} }
} }
_playerRunning = true;
await AudioService.play(); await AudioService.play();
AudioService.currentMediaItemStream AudioService.currentMediaItemStream
.where((event) => event != null) .where((event) => event != null)
.listen((item) async { .listen((item) async {
EpisodeBrief episode = await dbHelper.getRssItemWithMediaId(item.id); EpisodeBrief episode = await dbHelper.getRssItemWithMediaId(item.id);
_backgroundAudioDuration = item.duration?.inMilliseconds ?? 0;
if (episode != null) { if (episode != null) {
_episode = episode; _episode = episode;
_backgroundAudioDuration = item?.duration ?? 0; _backgroundAudioDuration = item.duration.inMilliseconds ?? 0;
if (position > 0 && if (position > 0 &&
_backgroundAudioDuration > 0 && _backgroundAudioDuration > 0 &&
_episode.enclosureUrl == url) { _episode.enclosureUrl == url) {
AudioService.seekTo(position); AudioService.seekTo(Duration(milliseconds: position));
position = 0; position = 0;
} }
notifyListeners(); notifyListeners();
} else { } else {
_queue.playlist.removeAt(0); // _queue.playlist.removeAt(0);
AudioService.skipToNext(); AudioService.skipToNext();
} }
}); });
queueSubject = BehaviorSubject<List<MediaItem>>(); // queueSubject = BehaviorSubject<List<MediaItem>>();
queueSubject.addStream( // queueSubject.addStream(
AudioService.queueStream.distinct().where((event) => event != null)); // AudioService.queueStream.distinct().where((event) => event != null));
queueSubject.stream.listen((event) { //queueSubject.stream.
if (event.length == _queue.playlist.length - 1 && AudioService.customEventStream.distinct().listen((event) async {
_audioState == BasicPlaybackState.skippingToNext) { if (event is String && _episode.title == event) {
if (event.length == 0 || _stopOnComplete) { print(event);
_queue.delFromPlaylist(_episode); _queue.delFromPlaylist(_episode);
_lastPostion = 0; _lastPostion = 0;
notifyListeners(); notifyListeners();
positionStorage.saveInt(_lastPostion); await positionStorage.saveInt(_lastPostion);
final PlayHistory history = PlayHistory( final PlayHistory history = PlayHistory(
_episode.title, _episode.title,
_episode.enclosureUrl, _episode.enclosureUrl,
backgroundAudioPosition / 1000, backgroundAudioPosition / 1000,
seekSliderValue); seekSliderValue);
dbHelper.saveHistory(history); dbHelper.saveHistory(history);
} else if (event.first.id != _episode.mediaId) {
_lastPostion = 0;
notifyListeners();
positionStorage.saveInt(_lastPostion);
_queue.delFromPlaylist(_episode);
final PlayHistory history = PlayHistory(
_episode.title,
_episode.enclosureUrl,
backgroundAudioPosition / 1000,
seekSliderValue);
dbHelper.saveHistory(history);
}
} }
}); });
AudioService.playbackStateStream.listen((event) async { // AudioService.queueStream
// .distinct()
// .where((event) => event != null)
// .listen((event) {
// if (event.length == _queue.playlist.length - 1 &&
// _audioState == AudioProcessingState.skippingToNext) {
// if (event.length == 0 || _stopOnComplete) {
// _queue.delFromPlaylist(_episode);
// _lastPostion = 0;
// notifyListeners();
// positionStorage.saveInt(_lastPostion);
// final PlayHistory history = PlayHistory(
// _episode.title,
// _episode.enclosureUrl,
// backgroundAudioPosition / 1000,
// seekSliderValue);
// dbHelper.saveHistory(history);
// } else if (event.first.id != _episode.mediaId) {
// _lastPostion = 0;
// notifyListeners();
// positionStorage.saveInt(_lastPostion);
// _queue.delFromPlaylist(_episode);
// final PlayHistory history = PlayHistory(
// _episode.title,
// _episode.enclosureUrl,
// backgroundAudioPosition / 1000,
// seekSliderValue);
// dbHelper.saveHistory(history);
// }
// }
// });
AudioService.playbackStateStream
.distinct()
.where((event) => event != null)
.listen((event) async {
_current = DateTime.now(); _current = DateTime.now();
_audioState = event?.basicState; _audioState = event?.processingState;
if (_audioState == BasicPlaybackState.stopped) { _playing = event?.playing;
_playerRunning = false; _currentSpeed = event.speed;
_currentPosition = event.currentPosition.inMilliseconds ?? 0;
if (_audioState == AudioProcessingState.stopped) {
if (_switchValue > 0) _switchValue = 0; if (_switchValue > 0) _switchValue = 0;
} }
if (_audioState == BasicPlaybackState.error) { /// Get error state.
if (_audioState == AudioProcessingState.error) {
_remoteErrorMessage = 'Network Error'; _remoteErrorMessage = 'Network Error';
} }
if (_audioState != BasicPlaybackState.error &&
_audioState != BasicPlaybackState.paused) { /// Reset error state.
if (_audioState != AudioProcessingState.error && _playing) {
_remoteErrorMessage = null; _remoteErrorMessage = null;
} }
_currentPosition = event?.currentPosition ?? 0;
notifyListeners(); notifyListeners();
}); });
@ -407,7 +403,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
Timer.periodic(Duration(milliseconds: 500), (timer) { Timer.periodic(Duration(milliseconds: 500), (timer) {
double s = _currentSpeed ?? 1.0; double s = _currentSpeed ?? 1.0;
if (_noSlide) { if (_noSlide) {
if (_audioState == BasicPlaybackState.playing) { if (_playing) {
getPosition = _currentPosition + getPosition = _currentPosition +
((DateTime.now().difference(_current).inMilliseconds) * s) ((DateTime.now().difference(_current).inMilliseconds) * s)
.toInt(); .toInt();
@ -432,7 +428,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
} }
notifyListeners(); notifyListeners();
} }
if (_audioState == BasicPlaybackState.stopped) { if (_audioState == AudioProcessingState.stopped) {
timer.cancel(); timer.cancel();
} }
}); });
@ -444,9 +440,8 @@ class AudioPlayerNotifier extends ChangeNotifier {
_backgroundAudioPosition = 0; _backgroundAudioPosition = 0;
_seekSliderValue = 0; _seekSliderValue = 0;
_episode = _queue.playlist.first; _episode = _queue.playlist.first;
_playerRunning = true;
_audioState = BasicPlaybackState.connecting;
_queueUpdate = !_queueUpdate; _queueUpdate = !_queueUpdate;
_audioState = AudioProcessingState.connecting;
notifyListeners(); notifyListeners();
_startAudioService(_lastPostion ?? 0, _queue.playlist.first.enclosureUrl); _startAudioService(_lastPostion ?? 0, _queue.playlist.first.enclosureUrl);
} }
@ -457,7 +452,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
addToPlaylist(EpisodeBrief episode) async { addToPlaylist(EpisodeBrief episode) async {
if (!_queue.playlist.contains(episode)) { if (!_queue.playlist.contains(episode)) {
if (_playerRunning) { if (playerRunning) {
await AudioService.addQueueItem(episode.toMediaItem()); await AudioService.addQueueItem(episode.toMediaItem());
} }
await _queue.addToPlayList(episode); await _queue.addToPlayList(episode);
@ -466,7 +461,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
} }
addToPlaylistAt(EpisodeBrief episode, int index) async { addToPlaylistAt(EpisodeBrief episode, int index) async {
if (_playerRunning) { if (playerRunning) {
await AudioService.addQueueItemAt(episode.toMediaItem(), index); await AudioService.addQueueItemAt(episode.toMediaItem(), index);
} }
await _queue.addToPlayListAt(episode, index); await _queue.addToPlayListAt(episode, index);
@ -500,7 +495,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
} }
Future<int> delFromPlaylist(EpisodeBrief episode) async { Future<int> delFromPlaylist(EpisodeBrief episode) async {
if (_playerRunning) { if (playerRunning) {
await AudioService.removeQueueItem(episode.toMediaItem()); await AudioService.removeQueueItem(episode.toMediaItem());
} }
int index = await _queue.delFromPlaylist(episode); int index = await _queue.delFromPlaylist(episode);
@ -511,7 +506,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
moveToTop(EpisodeBrief episode) async { moveToTop(EpisodeBrief episode) async {
await delFromPlaylist(episode); await delFromPlaylist(episode);
if (_playerRunning) { if (playerRunning) {
await addToPlaylistAt(episode, 1); await addToPlaylistAt(episode, 1);
} else { } else {
await addToPlaylistAt(episode, 0); await addToPlaylistAt(episode, 0);
@ -526,29 +521,29 @@ class AudioPlayerNotifier extends ChangeNotifier {
} }
resumeAudio() async { resumeAudio() async {
if (_audioState != BasicPlaybackState.connecting && if (_audioState != AudioProcessingState.connecting &&
_audioState != BasicPlaybackState.none) AudioService.play(); _audioState != AudioProcessingState.none) AudioService.play();
} }
forwardAudio(int s) { forwardAudio(int s) {
int pos = _backgroundAudioPosition + s * 1000; int pos = _backgroundAudioPosition + s * 1000;
AudioService.seekTo(pos); AudioService.seekTo(Duration(milliseconds: pos));
} }
seekTo(int position) async { seekTo(int position) async {
if (_audioState != BasicPlaybackState.connecting && if (_audioState != AudioProcessingState.connecting &&
_audioState != BasicPlaybackState.none) _audioState != AudioProcessingState.none)
await AudioService.seekTo(position); await AudioService.seekTo(Duration(milliseconds: position));
} }
sliderSeek(double val) async { sliderSeek(double val) async {
if (_audioState != BasicPlaybackState.connecting && if (_audioState != AudioProcessingState.connecting &&
_audioState != BasicPlaybackState.none) { _audioState != AudioProcessingState.none) {
_noSlide = false; _noSlide = false;
_seekSliderValue = val; _seekSliderValue = val;
notifyListeners(); notifyListeners();
_currentPosition = (val * _backgroundAudioDuration).toInt(); _currentPosition = (val * _backgroundAudioDuration).toInt();
await AudioService.seekTo(_currentPosition); await AudioService.seekTo(Duration(milliseconds: _currentPosition));
_noSlide = true; _noSlide = true;
} }
} }
@ -579,9 +574,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
_stopOnComplete = false; _stopOnComplete = false;
_startSleepTimer = false; _startSleepTimer = false;
_switchValue = 0; _switchValue = 0;
_playerRunning = false; //_playerRunning = false;
notifyListeners();
AudioService.stop(); AudioService.stop();
notifyListeners();
AudioService.disconnect(); AudioService.disconnect();
}); });
} else if (_sleepTimerMode == SleepTimerMode.endOfEpisode) { } else if (_sleepTimerMode == SleepTimerMode.endOfEpisode) {
@ -610,51 +605,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
} }
} }
shareClip(int start, int duration) async {
_shareStatus = ShareStatus.generate;
notifyListeners();
int length = math.min(duration, (_backgroundAudioDuration ~/ 1000 - start));
final BaseOptions options = BaseOptions(
connectTimeout: 60000,
receiveTimeout: 120000,
);
String imageUrl = await dbHelper.getImageUrl(_episode.enclosureUrl);
String url = "https://podcastapi.stonegate.me/clip?" +
"audio_link=${_episode.enclosureUrl}&image_link=$imageUrl&title=${_episode.feedTitle}" +
"&text=${_episode.title}&start=$start&length=$length";
String shareKey = environment['shareKey'];
try {
Response response = await Dio(options).get(url,
options: Options(headers: {
'X-Share-Key': "$shareKey",
}));
String shareLink = response.data;
print(shareLink);
String fileName = _episode.title + start.toString() + '.mp4';
_shareStatus = ShareStatus.download;
notifyListeners();
Directory dir = await getTemporaryDirectory();
String shareDir = join(dir.path, 'share', fileName);
try {
await Dio().download(shareLink, shareDir);
_shareFile = shareDir;
_shareStatus = ShareStatus.complete;
notifyListeners();
} on DioError catch (e) {
print(e);
_shareStatus = ShareStatus.error;
notifyListeners();
}
} catch (e) {
print(e);
_shareStatus = ShareStatus.error;
notifyListeners();
}
}
@override @override
void dispose() async { void dispose() async {
await AudioService.stop(); // await AudioService.stop();
await AudioService.disconnect(); await AudioService.disconnect();
//_playerRunning = false; //_playerRunning = false;
super.dispose(); super.dispose();
@ -666,66 +619,57 @@ class AudioPlayerTask extends BackgroundAudioTask {
List<MediaItem> _queue = []; List<MediaItem> _queue = [];
AudioPlayer _audioPlayer = AudioPlayer(); AudioPlayer _audioPlayer = AudioPlayer();
Completer _completer = Completer(); AudioProcessingState _skipState;
BasicPlaybackState _skipState;
bool _lostFocus;
bool _playing; bool _playing;
bool _interrupted = false;
bool _stopAtEnd; bool _stopAtEnd;
int _cacheMax; 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;
BasicPlaybackState _stateToBasicState(AudioPlaybackState state) { StreamSubscription<AudioPlaybackState> _playerStateSubscription;
switch (state) { StreamSubscription<AudioPlaybackEvent> _eventSubscription;
case AudioPlaybackState.none:
return BasicPlaybackState.none;
case AudioPlaybackState.stopped:
return _skipState ?? BasicPlaybackState.stopped;
case AudioPlaybackState.paused:
return BasicPlaybackState.paused;
case AudioPlaybackState.playing:
return BasicPlaybackState.playing;
case AudioPlaybackState.connecting:
return _skipState ?? BasicPlaybackState.connecting;
case AudioPlaybackState.completed:
return BasicPlaybackState.stopped;
default:
throw Exception("Illegal state");
}
}
@override @override
Future<void> onStart() async { Future<void> onStart(Map<String, dynamic> params) async {
_stopAtEnd = false; _stopAtEnd = false;
_lostFocus = false; _playerStateSubscription = _audioPlayer.playbackStateStream
var playerStateSubscription = _audioPlayer.playbackStateStream
.where((state) => state == AudioPlaybackState.completed) .where((state) => state == AudioPlaybackState.completed)
.listen((state) { .listen((state) {
_handlePlaybackCompleted(); _handlePlaybackCompleted();
}); });
var eventSubscription = _audioPlayer.playbackEventStream.listen((event) {
_eventSubscription = _audioPlayer.playbackEventStream.listen((event) {
if (event.playbackError != null) { if (event.playbackError != null) {
_setState(state: _skipState ?? BasicPlaybackState.error); _playing = false;
_setState(processingState: _skipState ?? AudioProcessingState.error);
} }
BasicPlaybackState state; final bufferingState =
if (event.buffering) { event.buffering ? AudioProcessingState.buffering : null;
state = _skipState ?? BasicPlaybackState.buffering; switch (event.state) {
} else { case AudioPlaybackState.paused:
state = _stateToBasicState(event.state); _setState(
} processingState: bufferingState ?? AudioProcessingState.ready,
if (state != BasicPlaybackState.stopped) { position: event.position,
_setState( );
state: state, break;
position: event.position.inMilliseconds, case AudioPlaybackState.playing:
speed: event.speed, _setState(
); processingState: bufferingState ?? AudioProcessingState.ready,
position: event.position,
);
break;
case AudioPlaybackState.connecting:
_setState(
processingState: _skipState ?? AudioProcessingState.connecting,
position: event.position,
);
break;
default:
break;
} }
}); });
await _completer.future;
playerStateSubscription.cancel();
eventSubscription.cancel();
} }
void _handlePlaybackCompleted() async { void _handlePlaybackCompleted() async {
@ -740,7 +684,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
} }
void playPause() { void playPause() {
if (AudioServiceBackground.state.basicState == BasicPlaybackState.playing) if (AudioServiceBackground.state.playing)
onPause(); onPause();
else else
onPlay(); onPlay();
@ -748,24 +692,27 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override @override
Future<void> onSkipToNext() async { Future<void> onSkipToNext() async {
_skipState = BasicPlaybackState.skippingToNext; _skipState = AudioProcessingState.skippingToNext;
_playing = false;
await _audioPlayer.stop(); await _audioPlayer.stop();
if (_queue.length > 0) _queue.removeAt(0); if (_queue.length > 0) {
AudioServiceBackground.sendCustomEvent(_queue.first.title);
_queue.removeAt(0);
}
await AudioServiceBackground.setQueue(_queue); await AudioServiceBackground.setQueue(_queue);
// } // }
if (_queue.length == 0 || _stopAtEnd) { if (_queue.length == 0 || _stopAtEnd) {
// await Future.delayed(Duration(milliseconds: 300));
_skipState = null; _skipState = null;
onStop(); onStop();
} else { } else {
await AudioServiceBackground.setQueue(_queue); await AudioServiceBackground.setQueue(_queue);
await AudioServiceBackground.setMediaItem(mediaItem); await AudioServiceBackground.setMediaItem(mediaItem);
await _audioPlayer.setUrl(mediaItem.id, _cacheMax); await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax);
print(mediaItem.title); print(mediaItem.title);
Duration duration = await _audioPlayer.durationFuture; Duration duration = await _audioPlayer.durationFuture;
if (duration != null) if (duration != null)
await AudioServiceBackground.setMediaItem( await AudioServiceBackground.setMediaItem(
mediaItem.copyWith(duration: duration.inMilliseconds)); mediaItem.copyWith(duration: duration));
_skipState = null; _skipState = null;
// Resume playback if we were playing // Resume playback if we were playing
// if (_playing) { // if (_playing) {
@ -784,35 +731,22 @@ class AudioPlayerTask extends BackgroundAudioTask {
_playing = true; _playing = true;
_cacheMax = await cacheStorage.getInt( _cacheMax = await cacheStorage.getInt(
defaultValue: (200 * 1024 * 1024).toInt()); defaultValue: (200 * 1024 * 1024).toInt());
// await AudioServiceBackground.setQueue(_queue);
if (_cacheMax == 0) { if (_cacheMax == 0) {
await cacheStorage.saveInt((200 * 1024 * 1024).toInt()); await cacheStorage.saveInt((200 * 1024 * 1024).toInt());
_cacheMax = 200 * 1024 * 1024; _cacheMax = 200 * 1024 * 1024;
} }
await _audioPlayer.setUrl(mediaItem.id, _cacheMax); await _audioPlayer.setUrl(mediaItem.id, cacheMax: _cacheMax);
var duration = await _audioPlayer.durationFuture; var duration = await _audioPlayer.durationFuture;
if (duration != null) if (duration != null)
await AudioServiceBackground.setMediaItem( await AudioServiceBackground.setMediaItem(
mediaItem.copyWith(duration: duration.inMilliseconds)); mediaItem.copyWith(duration: duration));
playFromStart(); playFromStart();
} } else {
// if (mediaItem.extras['skip'] > 0) {
// await _audioPlayer.setClip(
// start: Duration(seconds: 60));
// print(mediaItem.extras['skip']);
// print('set clip success');
// }
else {
_playing = true; _playing = true;
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting || if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none) _audioPlayer.playbackEvent.state != AudioPlaybackState.none)
_audioPlayer.play(); _audioPlayer.play();
} }
// if (mediaItem.extras['skip'] >
// _audioPlayer.playbackEvent.position.inSeconds ??
// 0) {
// _audioPlayer.seek(Duration(seconds: mediaItem.extras['skip']));
// }
} }
} }
@ -823,7 +757,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
try { try {
_audioPlayer.play(); _audioPlayer.play();
} catch (e) { } catch (e) {
_setState(state: BasicPlaybackState.error); _setState(processingState: AudioProcessingState.error);
} }
if (mediaItem.extras['skip'] > 0) { if (mediaItem.extras['skip'] > 0) {
_audioPlayer.seek(Duration(seconds: mediaItem.extras['skip'])); _audioPlayer.seek(Duration(seconds: mediaItem.extras['skip']));
@ -834,8 +768,7 @@ class AudioPlayerTask extends BackgroundAudioTask {
void onPause() { void onPause() {
if (_skipState == null) { if (_skipState == null) {
if (_playing == null) { if (_playing == null) {
} else if (_audioPlayer.playbackEvent.state == } else if (_playing) {
AudioPlaybackState.playing) {
_playing = false; _playing = false;
_audioPlayer.pause(); _audioPlayer.pause();
} }
@ -843,10 +776,10 @@ class AudioPlayerTask extends BackgroundAudioTask {
} }
@override @override
void onSeekTo(int position) { void onSeekTo(Duration position) {
if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting || if (_audioPlayer.playbackEvent.state != AudioPlaybackState.connecting ||
_audioPlayer.playbackEvent.state != AudioPlaybackState.none) _audioPlayer.playbackEvent.state != AudioPlaybackState.none)
_audioPlayer.seek(Duration(milliseconds: position)); _audioPlayer.seek(position);
} }
@override @override
@ -854,20 +787,26 @@ class AudioPlayerTask extends BackgroundAudioTask {
if (button == MediaButton.media) if (button == MediaButton.media)
playPause(); playPause();
else if (button == MediaButton.next) else if (button == MediaButton.next)
_audioPlayer.seek(Duration( _seekRelative(fastForwardInterval);
milliseconds: AudioServiceBackground.state.position + 30 * 1000)); else if (button == MediaButton.previous) _seekRelative(-rewindInterval);
else if (button == MediaButton.previous) }
_audioPlayer.seek(Duration(
milliseconds: AudioServiceBackground.state.position - 10 * 1000)); Future<void> _seekRelative(Duration offset) async {
var newPosition = _audioPlayer.playbackEvent.position + offset;
if (newPosition < Duration.zero) newPosition = Duration.zero;
if (newPosition > mediaItem.duration) newPosition = mediaItem.duration;
await _audioPlayer.seek(newPosition);
} }
@override @override
void onStop() async { Future<void> onStop() async {
await _audioPlayer.stop(); await _audioPlayer.stop();
await _audioPlayer.dispose(); await _audioPlayer.dispose();
_setState(state: BasicPlaybackState.stopped); _playing = false;
await Future.delayed(Duration(milliseconds: 300)); _playerStateSubscription.cancel();
_completer?.complete(); _eventSubscription.cancel();
await _setState(processingState: AudioProcessingState.none);
await super.onStop();
} }
@override @override
@ -890,10 +829,10 @@ 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, _cacheMax); await _audioPlayer.setUrl(mediaItem.id, cacheMax: _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));
playFromStart(); playFromStart();
//onPlay(); //onPlay();
} else { } else {
@ -904,26 +843,26 @@ class AudioPlayerTask extends BackgroundAudioTask {
@override @override
void onFastForward() { void onFastForward() {
_audioPlayer.seek(Duration( _seekRelative(fastForwardInterval);
milliseconds: AudioServiceBackground.state.position + 30 * 1000));
} }
@override @override
void onRewind() { void onRewind() {
_audioPlayer.seek(Duration( _seekRelative(rewindInterval);
milliseconds: AudioServiceBackground.state.position - 10 * 1000));
} }
@override @override
void onAudioFocusLost() { void onAudioFocusLost(AudioInterruption interruption) {
if (_skipState == null) { if (_playing) _interrupted = true;
if (_playing == null) { switch (interruption) {
} else if (_audioPlayer.playbackEvent.state == case AudioInterruption.pause:
AudioPlaybackState.playing) { case AudioInterruption.temporaryPause:
_playing = false; case AudioInterruption.unknownPause:
_lostFocus = true; onPause();
_audioPlayer.pause(); break;
} case AudioInterruption.temporaryDuck:
_audioPlayer.setVolume(0.5);
break;
} }
} }
@ -940,14 +879,18 @@ class AudioPlayerTask extends BackgroundAudioTask {
} }
@override @override
void onAudioFocusGained() { void onAudioFocusGained(AudioInterruption interruption) {
if (_skipState == null) { switch (interruption) {
if (_lostFocus) { case AudioInterruption.temporaryPause:
_lostFocus = false; if (!_playing && _interrupted) onPlay();
_playing = true; break;
_audioPlayer.play(); case AudioInterruption.temporaryDuck:
} _audioPlayer.setVolume(1.0);
break;
default:
break;
} }
_interrupted = false;
} }
@override @override
@ -965,24 +908,27 @@ class AudioPlayerTask extends BackgroundAudioTask {
} }
} }
void _setState( Future<void> _setState({
{@required BasicPlaybackState state, int position, double speed}) { AudioProcessingState processingState,
Duration position,
Duration bufferedPosition,
}) async {
if (position == null) { if (position == null) {
position = _audioPlayer.playbackEvent.position.inMilliseconds; position = _audioPlayer.playbackEvent.position;
} }
if (speed == null) { await AudioServiceBackground.setState(
speed = _audioPlayer.playbackEvent.speed; controls: getControls(),
}
AudioServiceBackground.setState(
controls: getControls(state),
systemActions: [MediaAction.seekTo], systemActions: [MediaAction.seekTo],
basicState: state, processingState:
processingState ?? AudioServiceBackground.state.processingState,
playing: _playing,
position: position, position: position,
speed: speed, bufferedPosition: bufferedPosition ?? position,
speed: _audioPlayer.speed,
); );
} }
List<MediaControl> getControls(BasicPlaybackState state) { List<MediaControl> getControls() {
if (_playing) { if (_playing) {
return [pauseControl, forward30, skipToNextControl, stopControl]; return [pauseControl, forward30, skipToNextControl, stopControl];
} else { } else {

View File

@ -62,7 +62,7 @@ class EpisodeBrief {
title: title, title: title,
artist: feedTitle, artist: feedTitle,
album: feedTitle, album: feedTitle,
// duration: 0, duration: Duration.zero,
artUri: 'file://$imagePath', artUri: 'file://$imagePath',
extras: {'skip': skipSeconds}); extras: {'skip': skipSeconds});
} }

View File

@ -0,0 +1,19 @@
import '../local_storage/sqflite_localpodcast.dart';
import 'episodebrief.dart';
class PlayHistory {
DBHelper dbHelper = DBHelper();
String title;
String url;
double seconds;
double seekValue;
DateTime playdate;
PlayHistory(this.title, this.url, this.seconds, this.seekValue,
{this.playdate});
EpisodeBrief _episode;
EpisodeBrief get episode => _episode;
getEpisode() async {
_episode = await dbHelper.getRssItemWithUrl(url);
}
}

58
lib/type/playlist.dart Normal file
View File

@ -0,0 +1,58 @@
import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart';
import 'episodebrief.dart';
class Playlist {
String name;
DBHelper dbHelper = DBHelper();
List<EpisodeBrief> _playlist;
List<EpisodeBrief> get playlist => _playlist;
KeyValueStorage storage = KeyValueStorage('playlist');
getPlaylist() async {
List<String> urls = await storage.getStringList();
if (urls.length == 0) {
_playlist = [];
} else {
_playlist = [];
for (String url in urls) {
EpisodeBrief episode = await dbHelper.getRssItemWithUrl(url);
if (episode != null) _playlist.add(episode);
}
}
}
savePlaylist() async {
List<String> urls = [];
urls.addAll(_playlist.map((e) => e.enclosureUrl));
await storage.saveStringList(urls.toSet().toList());
}
addToPlayList(EpisodeBrief episodeBrief) async {
if (!_playlist.contains(episodeBrief)) {
_playlist.add(episodeBrief);
await savePlaylist();
dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl);
}
}
addToPlayListAt(EpisodeBrief episodeBrief, int index) async {
if (!_playlist.contains(episodeBrief)) {
_playlist.insert(index, episodeBrief);
await savePlaylist();
dbHelper.removeEpisodeNewMark(episodeBrief.enclosureUrl);
}
}
Future<int> delFromPlaylist(EpisodeBrief episodeBrief) async {
int index = _playlist.indexOf(episodeBrief);
_playlist.removeWhere(
(episode) => episode.enclosureUrl == episodeBrief.enclosureUrl);
print('delete' + episodeBrief.title);
await savePlaylist();
return index;
}
}

View File

@ -0,0 +1,41 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:tsacdop/type/searchpodcast.dart';
part 'searchepisodes.g.dart';
@JsonSerializable()
class SearchEpisodes<E> {
@_ConvertE()
final List<E> episodes;
@JsonKey(name: 'next_episode_pub_date')
final int nextEpisodeDate;
SearchEpisodes({this.episodes, this.nextEpisodeDate});
factory SearchEpisodes.fromJson(Map<String, dynamic> json) =>
_$SearchEpisodesFromJson<E>(json);
Map<String, dynamic> toJson() => _$SearchEpisodesToJson(this);
}
class _ConvertE<E> implements JsonConverter<E, Object> {
const _ConvertE();
@override
E fromJson(Object json) {
return OnlineEpisode.fromJson(json) as E;
}
@override
Object toJson(E object) {
return object;
}
}
@JsonSerializable()
class OnlineEpisode {
final String title;
@JsonKey(name: 'pub_date_ms')
final int pubDate;
@JsonKey(name: 'audio_length_sec')
final int length;
OnlineEpisode({this.title, this.pubDate, this.length});
factory OnlineEpisode.fromJson(Map<String, dynamic> json) =>
_$OnlineEpisodeFromJson(json);
Map<String, dynamic> toJson() => _$OnlineEpisodeToJson(this);
}

View File

@ -0,0 +1,36 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'searchepisodes.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SearchEpisodes<E> _$SearchEpisodesFromJson<E>(Map<String, dynamic> json) {
return SearchEpisodes<E>(
episodes:
(json['episodes'] as List)?.map(_ConvertE<E>().fromJson)?.toList(),
nextEpisodeDate: json['next_episode_pub_date'] as int,
);
}
Map<String, dynamic> _$SearchEpisodesToJson<E>(SearchEpisodes<E> instance) =>
<String, dynamic>{
'episodes': instance.episodes?.map(_ConvertE<E>().toJson)?.toList(),
'next_episode_pub_date': instance.nextEpisodeDate,
};
OnlineEpisode _$OnlineEpisodeFromJson(Map<String, dynamic> json) {
return OnlineEpisode(
title: json['title'] as String,
pubDate: json['pub_date_ms'] as int,
length: json['audio_length_sec'] as int,
);
}
Map<String, dynamic> _$OnlineEpisodeToJson(OnlineEpisode instance) =>
<String, dynamic>{
'title': instance.title,
'pub_date_ms': instance.pubDate,
'audio_length_sec': instance.length,
};

View File

@ -1,43 +1,44 @@
import 'dart:ui';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
part 'searchpodcast.g.dart'; part 'searchpodcast.g.dart';
@JsonSerializable() @JsonSerializable()
class SearchPodcast<P>{ class SearchPodcast<P> {
@_ConvertP() @_ConvertP()
final List<P> results; final List<P> results;
@JsonKey(name: 'next_offset') @JsonKey(name: 'next_offset')
final int nextOffset; final int nextOffset;
final int total; final int total;
final int count; final int count;
SearchPodcast( SearchPodcast({this.results, this.nextOffset, this.total, this.count});
{this.results, this.nextOffset, this.total, this.count} factory SearchPodcast.fromJson(Map<String, dynamic> json) =>
); _$SearchPodcastFromJson<P>(json);
factory SearchPodcast.fromJson(Map<String, dynamic> json) =>
_$SearchPodcastFromJson<P>(json);
Map<String, dynamic> toJson() => _$SearchPodcastToJson(this); Map<String, dynamic> toJson() => _$SearchPodcastToJson(this);
} }
class _ConvertP<P> implements JsonConverter<P, Object>{ class _ConvertP<P> implements JsonConverter<P, Object> {
const _ConvertP(); const _ConvertP();
@override @override
P fromJson(Object json){ P fromJson(Object json) {
return OnlinePodcast.fromJson(json) as P; return OnlinePodcast.fromJson(json) as P;
} }
@override @override
Object toJson(P object){ Object toJson(P object) {
return object; return object;
} }
} }
@JsonSerializable() @JsonSerializable()
class OnlinePodcast{ class OnlinePodcast {
@JsonKey(name: 'earliest_pub_date_ms') @JsonKey(name: 'earliest_pub_date_ms')
final int earliestPubDate; final int earliestPubDate;
@JsonKey(name: 'title_original') @JsonKey(name: 'title_original')
final String title; final String title;
final String rss; final String rss;
@JsonKey(name: 'lastest_pub_date_ms') @JsonKey(name: 'latest_pub_date_ms')
final int lastestPubDate; final int latestPubDate;
@JsonKey(name: 'description_original') @JsonKey(name: 'description_original')
final String description; final String description;
@JsonKey(name: 'total_episodes') @JsonKey(name: 'total_episodes')
@ -45,10 +46,30 @@ class OnlinePodcast{
final String image; final String image;
@JsonKey(name: 'publisher_original') @JsonKey(name: 'publisher_original')
final String publisher; final String publisher;
final String id;
OnlinePodcast( OnlinePodcast(
{this.earliestPubDate, this.title, this.count, this.description, this.image, this.lastestPubDate, this.rss, this.publisher} {this.earliestPubDate,
); this.title,
this.count,
this.description,
this.image,
this.latestPubDate,
this.rss,
this.publisher,
this.id});
factory OnlinePodcast.fromJson(Map<String, dynamic> json) => factory OnlinePodcast.fromJson(Map<String, dynamic> json) =>
_$OnlinePodcastFromJson(json); _$OnlinePodcastFromJson(json);
Map<String, dynamic> toJson() => _$OnlinePodcastToJson(this); Map<String, dynamic> toJson() => _$OnlinePodcastToJson(this);
}
@override
bool operator ==(Object onlinePodcast) =>
onlinePodcast is OnlinePodcast && onlinePodcast.id == id;
@override
int get hashCode => hashValues(id, title);
int get interval {
if (count < 1) return null;
return (latestPubDate - earliestPubDate) ~/ count;
}
}

View File

@ -8,34 +8,33 @@ part of 'searchpodcast.dart';
SearchPodcast<P> _$SearchPodcastFromJson<P>(Map<String, dynamic> json) { SearchPodcast<P> _$SearchPodcastFromJson<P>(Map<String, dynamic> json) {
return SearchPodcast<P>( return SearchPodcast<P>(
results: (json['results'] as List) results: (json['results'] as List)?.map(_ConvertP<P>().fromJson)?.toList(),
?.map((e) => e == null ? null : _ConvertP<P>().fromJson(e)) nextOffset: json['next_offset'] as int,
?.toList(), total: json['total'] as int,
nextOffset: json['next_offset'] as int, count: json['count'] as int,
total: json['total'] as int, );
count: json['count'] as int);
} }
Map<String, dynamic> _$SearchPodcastToJson<P>(SearchPodcast<P> instance) => Map<String, dynamic> _$SearchPodcastToJson<P>(SearchPodcast<P> instance) =>
<String, dynamic>{ <String, dynamic>{
'results': instance.results 'results': instance.results?.map(_ConvertP<P>().toJson)?.toList(),
?.map((e) => e == null ? null : _ConvertP<P>().toJson(e))
?.toList(),
'next_offset': instance.nextOffset, 'next_offset': instance.nextOffset,
'total': instance.total, 'total': instance.total,
'count': instance.count 'count': instance.count,
}; };
OnlinePodcast _$OnlinePodcastFromJson(Map<String, dynamic> json) { OnlinePodcast _$OnlinePodcastFromJson(Map<String, dynamic> json) {
return OnlinePodcast( return OnlinePodcast(
earliestPubDate: json['earliest_pub_date_ms'] as int, earliestPubDate: json['earliest_pub_date_ms'] as int,
title: json['title_original'] as String, title: json['title_original'] as String,
count: json['total_episodes'] as int, count: json['total_episodes'] as int,
description: json['description_original'] as String, description: json['description_original'] as String,
image: json['image'] as String, image: json['image'] as String,
lastestPubDate: json['lastest_pub_date_ms'] as int, latestPubDate: json['latest_pub_date_ms'] as int,
rss: json['rss'] as String, rss: json['rss'] as String,
publisher: json['publisher_original'] as String); publisher: json['publisher_original'] as String,
id: json['id'] as String,
);
} }
Map<String, dynamic> _$OnlinePodcastToJson(OnlinePodcast instance) => Map<String, dynamic> _$OnlinePodcastToJson(OnlinePodcast instance) =>
@ -43,9 +42,10 @@ Map<String, dynamic> _$OnlinePodcastToJson(OnlinePodcast instance) =>
'earliest_pub_date_ms': instance.earliestPubDate, 'earliest_pub_date_ms': instance.earliestPubDate,
'title_original': instance.title, 'title_original': instance.title,
'rss': instance.rss, 'rss': instance.rss,
'lastest_pub_date_ms': instance.lastestPubDate, 'latest_pub_date_ms': instance.latestPubDate,
'description_original': instance.description, 'description_original': instance.description,
'total_episodes': instance.count, 'total_episodes': instance.count,
'image': instance.image, 'image': instance.image,
'publisher_original': instance.publisher 'publisher_original': instance.publisher,
'id': instance.id,
}; };

View File

@ -34,12 +34,12 @@ class _AudioPanelState extends State<AudioPanel> with TickerProviderStateMixin {
_controller = _controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 50)) AnimationController(vsync: this, duration: Duration(milliseconds: 50))
..addListener(() { ..addListener(() {
setState(() {}); if (mounted) setState(() {});
}); });
_slowController = _slowController =
AnimationController(vsync: this, duration: Duration(milliseconds: 200)) AnimationController(vsync: this, duration: Duration(milliseconds: 200))
..addListener(() { ..addListener(() {
setState(() {}); if (mounted) setState(() {});
}); });
_animation = _animation =
Tween<double>(begin: initSize, end: initSize).animate(_controller); Tween<double>(begin: initSize, end: initSize).animate(_controller);

View File

@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import '../generated/l10n.dart';
extension ContextExtension on BuildContext {
Color get primaryColor => Theme.of(this).primaryColor;
Color get accentColor => Theme.of(this).accentColor;
Color get scaffoldBackgroundColor => Theme.of(this).scaffoldBackgroundColor;
Color get primaryColorDark => Theme.of(this).primaryColorDark;
Color get textColor => Theme.of(this).textTheme.bodyText1.color;
Brightness get brightness => Theme.of(this).brightness;
double get width => MediaQuery.of(this).size.width;
double get height => MediaQuery.of(this).size.width;
TextTheme get textTheme => Theme.of(this).textTheme;
S get s => S.of(this);
}

View File

@ -16,11 +16,12 @@ import 'open_container.dart';
import '../state/audio_state.dart'; import '../state/audio_state.dart';
import '../state/download_state.dart'; import '../state/download_state.dart';
import '../type/episodebrief.dart'; import '../type/episodebrief.dart';
import '../type/play_histroy.dart';
import '../episodes/episode_detail.dart'; import '../episodes/episode_detail.dart';
import '../local_storage/sqflite_localpodcast.dart'; import '../local_storage/sqflite_localpodcast.dart';
import '../local_storage/key_value_storage.dart'; import '../local_storage/key_value_storage.dart';
import 'colorize.dart'; import 'colorize.dart';
import 'context_extension.dart'; import 'extension_helper.dart';
import 'custompaint.dart'; import 'custompaint.dart';
enum Layout { three, two, one } enum Layout { three, two, one }

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../generated/l10n.dart';
extension ContextExtension on BuildContext {
Color get primaryColor => Theme.of(this).primaryColor;
Color get accentColor => Theme.of(this).accentColor;
Color get scaffoldBackgroundColor => Theme.of(this).scaffoldBackgroundColor;
Color get primaryColorDark => Theme.of(this).primaryColorDark;
Color get textColor => Theme.of(this).textTheme.bodyText1.color;
Brightness get brightness => Theme.of(this).brightness;
double get width => MediaQuery.of(this).size.width;
double get height => MediaQuery.of(this).size.height;
TextTheme get textTheme => Theme.of(this).textTheme;
S get s => S.of(this);
}
extension IntExtension on int {
String toDate(BuildContext context) {
if (this == null) return '';
final s = context.s;
DateTime date = DateTime.fromMillisecondsSinceEpoch(this, isUtc: true);
var difference = DateTime.now().toUtc().difference(date);
if (difference.inHours < 24) {
return s.hoursAgo(difference.inHours);
} else if (difference.inDays < 7) {
return s.daysAgo(difference.inDays);
} else {
return DateFormat.yMMMd().format(
DateTime.fromMillisecondsSinceEpoch(this, isUtc: true).toLocal());
}
}
String get toTime =>
'${(this ~/ 60)}:${(this.truncate() % 60).toString().padLeft(2, '0')}';
String toInterval(BuildContext context) {
if (this == null || this.isNegative) return '';
final s = context.s;
var interval = Duration(milliseconds: this);
if (interval.inHours <= 48)
return 'Published daily';
else if (interval.inDays > 2 && interval.inDays <= 14)
return 'Published weekly';
else if (interval.inDays > 14 && interval.inDays < 60)
return 'Published monthly';
else
return 'Published yearly';
}
}

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'context_extension.dart'; import 'extension_helper.dart';
generalDialog(BuildContext context, generalDialog(BuildContext context,
{Widget title, Widget content, List<Widget> actions}) => {Widget title, Widget content, List<Widget> actions}) =>
@ -26,7 +26,7 @@ generalDialog(BuildContext context,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0))), borderRadius: BorderRadius.all(Radius.circular(10.0))),
titlePadding: EdgeInsets.all(20), titlePadding: EdgeInsets.all(20),
title: SizedBox(width: context.width-160, child: title), title: SizedBox(width: context.width - 160, child: title),
content: content, content: content,
actionsPadding: EdgeInsets.all(10), actionsPadding: EdgeInsets.all(10),
actions: actions), actions: actions),

View File

@ -4,7 +4,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'context_extension.dart'; import 'extension_helper.dart';
/// Signature for a function that creates a [Widget] to be used within an /// Signature for a function that creates a [Widget] to be used within an
/// [OpenContainer]. /// [OpenContainer].

View File

@ -11,41 +11,43 @@ dependencies:
sdk: flutter sdk: flutter
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
cupertino_icons: ^0.1.3
json_annotation: ^3.0.1
sqflite: ^1.3.0
flutter_html: ^0.11.1
path_provider: ^1.6.8
color_thief_flutter: ^1.0.2
provider: ^4.1.2
google_fonts: ^1.1.0
dio: ^3.0.9
file_picker: ^1.9.0+1
marquee: ^1.3.1
flutter_downloader: ^1.4.4
permission_handler: ^5.0.0+hotfix.3
fluttertoast: ^4.0.1
intl: ^0.16.1
url_launcher: ^5.4.10
image: ^2.1.12
shared_preferences: ^0.5.7
uuid: ^2.2.0
tuple: ^1.0.3
cached_network_image: ^2.2.0+1
workmanager: ^0.2.3
fl_chart: ^0.10.1
audio_service: ^0.8.0
flutter_file_dialog: ^0.0.5
flutter_linkify: ^3.1.3
extended_nested_scroll_view: ^0.4.0
connectivity: ^0.4.8+2
flare_flutter: ^2.0.5
rxdart: ^0.24.0
wc_flutter_share: ^0.2.1
auto_animated: ^2.1.0 auto_animated: ^2.1.0
audio_service: ^0.11.2
cached_network_image: ^2.2.0+1
color_thief_flutter: ^1.0.2
cupertino_icons: ^0.1.3
connectivity: ^0.4.9
dio: ^3.0.9
extended_nested_scroll_view: ^1.0.1
feature_discovery: ^0.10.0 feature_discovery: ^0.10.0
file_picker: ^1.12.0
flutter_html: ^0.11.1
flutter_downloader: ^1.4.4
fluttertoast: ^4.0.0
flutter_isolate: ^1.0.0+14 flutter_isolate: ^1.0.0+14
flutter_time_picker_spinner: ^1.0.6+1 flutter_time_picker_spinner: ^1.0.6+1
flutter_linkify: ^3.1.3
flutter_file_dialog: ^0.0.5
flare_flutter: ^2.0.5
fl_chart: ^0.10.1
marquee: ^1.3.1
google_fonts: ^1.1.0
image: ^2.1.14
intl: ^0.16.1
json_serializable: ^3.3.0
json_annotation: ^3.0.1
path_provider: ^1.6.11
permission_handler: ^5.0.1
provider: ^4.3.1
rxdart: ^0.24.1
sqflite: ^1.3.1
shared_preferences: ^0.5.8
tuple: ^1.0.3
url_launcher: ^5.5.0
uuid: ^2.2.0
xml: ^4.2.0
workmanager: ^0.2.3
wc_flutter_share: ^0.2.2
just_audio: just_audio:
git: git:
url: https://github.com/stonega/just_audio.git url: https://github.com/stonega/just_audio.git
@ -59,9 +61,7 @@ dependencies:
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
build_runner: ^1.10.0
dependency_overrides:
xml: "4.2.0"
flutter: flutter:
assets: assets: