Play episode from search result.

This commit is contained in:
Stonegate 2021-02-09 15:36:29 +08:00
parent 5b2a95cd2d
commit 190f4b992d
8 changed files with 98 additions and 18 deletions

View File

@ -466,8 +466,11 @@ class _PlaylistWidgetState extends State<PlaylistWidget> {
child: SizedBox( child: SizedBox(
height: 30.0, height: 30.0,
width: 30.0, width: 30.0,
child: Image.file(File( child: Image(
"${episodes[index].imagePath}"))), image: episodes[index].avatarImage)
// Image.file(File(
// "${episodes[index].imagePath}"))
),
), ),
), ),
Expanded( Expanded(
@ -590,7 +593,7 @@ class SleepModeState extends State<SleepMode>
Future _getDefaultTime() async { Future _getDefaultTime() async {
var defaultSleepTimerStorage = KeyValueStorage(defaultSleepTimerKey); var defaultSleepTimerStorage = KeyValueStorage(defaultSleepTimerKey);
var defaultTime = await defaultSleepTimerStorage.getInt(defaultValue: 30); var defaultTime = await defaultSleepTimerStorage.getInt(defaultValue: 30);
if(mounted) setState(() => _minSelected = defaultTime); if (mounted) setState(() => _minSelected = defaultTime);
} }
@override @override
@ -992,7 +995,7 @@ class _ChaptersWidgetState extends State<ChaptersWidget> {
foregroundColor: MaterialStateProperty.all<Color>( foregroundColor: MaterialStateProperty.all<Color>(
context.accentColor), context.accentColor),
overlayColor: MaterialStateProperty.all<Color>( overlayColor: MaterialStateProperty.all<Color>(
context.primaryColor), context.primaryColor.withOpacity(0.3)),
), ),
onPressed: () => chapters.url.launchUrl, onPressed: () => chapters.url.launchUrl,
child: Text('Visit')), child: Text('Visit')),
@ -1093,7 +1096,7 @@ class _ChaptersWidgetState extends State<ChaptersWidget> {
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Text( Text(
context.s.settingsInfo, context.s.homeToprightMenuAbout,
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
style: TextStyle( style: TextStyle(
color: context.accentColor, color: context.accentColor,

View File

@ -10,7 +10,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tsacdop/type/episodebrief.dart'; import 'package:tsacdop/state/audio_state.dart';
import 'package:webfeed/webfeed.dart'; import 'package:webfeed/webfeed.dart';
import '../.env.dart'; import '../.env.dart';
@ -898,7 +898,7 @@ class _SearchResultDetailState extends State<SearchResultDetail>
var searchResult = await searchEngine.fetchEpisode(rssUrl: id); var searchResult = await searchEngine.fetchEpisode(rssUrl: id);
var episodes = searchResult.items.cast(); var episodes = searchResult.items.cast();
for (var episode in episodes) { for (var episode in episodes) {
_episodeList.add(episode.toOnlineWEpisode); _episodeList.add(episode.toOnlineEpisode);
} }
_loading = false; _loading = false;
return _episodeList; return _episodeList;
@ -1027,6 +1027,25 @@ class _SearchResultDetailState extends State<SearchResultDetail>
: '${content[index].length.toTime} | ' : '${content[index].length.toTime} | '
'${content[index].pubDate.toDate(context)}', '${content[index].pubDate.toDate(context)}',
style: TextStyle(color: context.accentColor)), style: TextStyle(color: context.accentColor)),
trailing: TextButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(
context.accentColor),
overlayColor: MaterialStateProperty.all<Color>(
context.primaryColor.withOpacity(0.3)),
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
EdgeInsets.symmetric(horizontal: 2))),
child: Text(context.s.play),
onPressed: () {
context.read<AudioPlayerNotifier>().episodeLoad(
content[index].toEpisode,
fromSearch: true);
Fluttertoast.showToast(
msg: 'Wait a moment',
gravity: ToastGravity.BOTTOM,
);
},
),
); );
}, },
); );
@ -1106,6 +1125,8 @@ class _SearchResultDetailState extends State<SearchResultDetail>
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
alignment: Alignment.center, alignment: Alignment.center,
imageUrl: widget.onlinePodcast.image, imageUrl: widget.onlinePodcast.image,
fadeInDuration: Duration.zero,
placeholderFadeInDuration: Duration.zero,
progressIndicatorBuilder: progressIndicatorBuilder:
(context, url, downloadProgress) => Container( (context, url, downloadProgress) => Container(
height: 120, height: 120,

View File

@ -175,6 +175,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
bool _markListened; bool _markListened;
// Tmep episode list, playing from search result
List<EpisodeBrief> _playFromSearchList = [];
@override @override
void addListener(VoidCallback listener) { void addListener(VoidCallback listener) {
super.addListener(listener); super.addListener(listener);
@ -279,7 +282,7 @@ class AudioPlayerNotifier extends ChangeNotifier {
await _playlist.getPlaylist(); await _playlist.getPlaylist();
if (state[1] != '') { if (state[1] != '') {
var episode = await _dbHelper.getRssItemWithUrl(state[1]); var episode = await _dbHelper.getRssItemWithUrl(state[1]);
if ((!_playlist.isQueue && _playlist.contains(episode)) || if ((!_playlist.isQueue && episode != null && _playlist.contains(episode)) ||
(_playlist.isQueue && (_playlist.isQueue &&
_queue.isNotEmpty && _queue.isNotEmpty &&
_queue.episodes.first.title == episode.title)) { _queue.episodes.first.title == episode.title)) {
@ -358,15 +361,21 @@ class AudioPlayerNotifier extends ChangeNotifier {
} }
Future<void> episodeLoad(EpisodeBrief episode, Future<void> episodeLoad(EpisodeBrief episode,
{int startPosition = 0}) async { {int startPosition = 0, bool fromSearch = false}) async {
final episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl); var episodeNew;
if (fromSearch) {
episodeNew = episode;
_playFromSearchList.add(episode);
} else {
episodeNew = await _dbHelper.getRssItemWithUrl(episode.enclosureUrl);
}
//TODO load episode from last position when player running //TODO load episode from last position when player running
if (playerRunning) { if (playerRunning) {
final history = PlayHistory(_episode.title, _episode.enclosureUrl, final history = PlayHistory(_episode.title, _episode.enclosureUrl,
backgroundAudioPosition ~/ 1000, seekSliderValue); backgroundAudioPosition ~/ 1000, seekSliderValue);
await _dbHelper.saveHistory(history); await _dbHelper.saveHistory(history);
_queue.addToPlayListAt(episodeNew, 0); _queue.addToPlayListAt(episodeNew, 0);
await updatePlaylist(_queue); await updatePlaylist(_queue, updateEpisodes: !fromSearch);
if (!_playlist.isQueue) { if (!_playlist.isQueue) {
AudioService.customAction('setIsQueue', true); AudioService.customAction('setIsQueue', true);
AudioService.customAction('changeQueue', [ AudioService.customAction('changeQueue', [
@ -486,7 +495,9 @@ class AudioPlayerNotifier extends ChangeNotifier {
.where((event) => event != null) .where((event) => event != null)
.listen((item) async { .listen((item) async {
var episode = await _dbHelper.getRssItemWithMediaId(item.id); var episode = await _dbHelper.getRssItemWithMediaId(item.id);
if(episode == null){
episode = _playFromSearchList.firstWhere((e) => e.mediaId == item.id, orElse: () => null);
}
_backgroundAudioDuration = item.duration?.inMilliseconds ?? 0; _backgroundAudioDuration = item.duration?.inMilliseconds ?? 0;
if (episode != null) { if (episode != null) {
_episode = episode; _episode = episode;

View File

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
@ -55,7 +56,7 @@ class EpisodeBrief extends Equatable {
artist: feedTitle, artist: feedTitle,
album: feedTitle, album: feedTitle,
duration: Duration.zero, duration: Duration.zero,
artUri: 'file://$imagePath', artUri: imagePath == '' ? episodeImage : 'file://$imagePath',
extras: { extras: {
'skipSecondsStart': skipSecondsStart, 'skipSecondsStart': skipSecondsStart,
'skipSecondsEnd': skipSecondsEnd 'skipSecondsEnd': skipSecondsEnd
@ -65,7 +66,9 @@ class EpisodeBrief extends Equatable {
ImageProvider get avatarImage { ImageProvider get avatarImage {
return File(imagePath).existsSync() return File(imagePath).existsSync()
? FileImage(File(imagePath)) ? FileImage(File(imagePath))
: const AssetImage('assets/avatar_backup.png'); : episodeImage != ''
? CachedNetworkImageProvider(episodeImage)
: AssetImage('assets/avatar_backup.png');
} }
Color backgroudColor(BuildContext context) { Color backgroudColor(BuildContext context) {

View File

@ -35,17 +35,25 @@ class IndexEpisode {
final int datePublished; final int datePublished;
final String enclosureUrl; final String enclosureUrl;
final int enclosureLength; final int enclosureLength;
final int duration;
final String feedImage;
IndexEpisode( IndexEpisode(
{this.title, {this.title,
this.description, this.description,
this.datePublished, this.datePublished,
this.enclosureLength, this.enclosureLength,
this.enclosureUrl}); this.enclosureUrl,
this.duration,
this.feedImage});
factory IndexEpisode.fromJson(Map<String, dynamic> json) => factory IndexEpisode.fromJson(Map<String, dynamic> json) =>
_$IndexEpisodeFromJson(json); _$IndexEpisodeFromJson(json);
Map<String, dynamic> toJson() => _$IndexEpisodeToJson(this); Map<String, dynamic> toJson() => _$IndexEpisodeToJson(this);
OnlineEpisode get toOnlineWEpisode => OnlineEpisode get toOnlineEpisode => OnlineEpisode(
OnlineEpisode(title: title, pubDate: datePublished * 1000, length: 0); title: title,
pubDate: datePublished * 1000,
length: duration ?? 0,
audio: enclosureUrl,
thumbnail: feedImage);
} }

View File

@ -30,6 +30,8 @@ IndexEpisode _$IndexEpisodeFromJson(Map<String, dynamic> json) {
datePublished: json['datePublished'] as int, datePublished: json['datePublished'] as int,
enclosureLength: json['enclosureLength'] as int, enclosureLength: json['enclosureLength'] as int,
enclosureUrl: json['enclosureUrl'] as String, enclosureUrl: json['enclosureUrl'] as String,
duration: json['duration'] as int,
feedImage: json['feedImage'] as String,
); );
} }
@ -40,4 +42,6 @@ Map<String, dynamic> _$IndexEpisodeToJson(IndexEpisode instance) =>
'datePublished': instance.datePublished, 'datePublished': instance.datePublished,
'enclosureUrl': instance.enclosureUrl, 'enclosureUrl': instance.enclosureUrl,
'enclosureLength': instance.enclosureLength, 'enclosureLength': instance.enclosureLength,
'duration': instance.duration,
'feedImage': instance.feedImage,
}; };

View File

@ -1,4 +1,7 @@
import 'dart:convert';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:tsacdop/type/episodebrief.dart';
part 'searchepisodes.g.dart'; part 'searchepisodes.g.dart';
@JsonSerializable() @JsonSerializable()
@ -33,8 +36,31 @@ class OnlineEpisode {
final int pubDate; final int pubDate;
@JsonKey(name: 'audio_length_sec') @JsonKey(name: 'audio_length_sec')
final int length; final int length;
OnlineEpisode({this.title, this.pubDate, this.length}); final String audio;
final String thumbnail;
OnlineEpisode({this.title, this.pubDate, this.length, this.audio, this.thumbnail});
factory OnlineEpisode.fromJson(Map<String, dynamic> json) => factory OnlineEpisode.fromJson(Map<String, dynamic> json) =>
_$OnlineEpisodeFromJson(json); _$OnlineEpisodeFromJson(json);
Map<String, dynamic> toJson() => _$OnlineEpisodeToJson(this); Map<String, dynamic> toJson() => _$OnlineEpisodeToJson(this);
EpisodeBrief get toEpisode {
return EpisodeBrief(
title,
audio,
0,
pubDate,
title,
jsonEncode([120,220,128]),
length ?? 0,
0,
'',
0,
mediaId: audio,
skipSecondsEnd: 0,
skipSecondsStart: 0,
chapterLink: '',
episodeImage: thumbnail
);
}
} }

View File

@ -25,6 +25,8 @@ OnlineEpisode _$OnlineEpisodeFromJson(Map<String, dynamic> json) {
title: json['title'] as String, title: json['title'] as String,
pubDate: json['pub_date_ms'] as int, pubDate: json['pub_date_ms'] as int,
length: json['audio_length_sec'] as int, length: json['audio_length_sec'] as int,
audio: json['audio'] as String,
thumbnail: json['thumbnail'] as String,
); );
} }
@ -33,4 +35,6 @@ Map<String, dynamic> _$OnlineEpisodeToJson(OnlineEpisode instance) =>
'title': instance.title, 'title': instance.title,
'pub_date_ms': instance.pubDate, 'pub_date_ms': instance.pubDate,
'audio_length_sec': instance.length, 'audio_length_sec': instance.length,
'audio': instance.audio,
'thumbnail': instance.thumbnail,
}; };