Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
flutter 3.27.1-stable
5 changes: 4 additions & 1 deletion lib/bloc/podcast/episode_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:async';
import 'package:anytime/bloc/bloc.dart';
import 'package:anytime/entities/episode.dart';
import 'package:anytime/services/audio/audio_player_service.dart';
import 'package:anytime/services/download/download_service.dart';
import 'package:anytime/services/podcast/podcast_service.dart';
import 'package:anytime/state/bloc_state.dart';
import 'package:logging/logging.dart';
Expand All @@ -17,6 +18,7 @@ import 'package:rxdart/rxdart.dart';
class EpisodeBloc extends Bloc {
final log = Logger('EpisodeBloc');
final PodcastService podcastService;
final DownloadService downloadService;
final AudioPlayerService audioPlayerService;

/// Add to sink to fetch list of current downloaded episodes.
Expand All @@ -43,6 +45,7 @@ class EpisodeBloc extends Bloc {
EpisodeBloc({
required this.podcastService,
required this.audioPlayerService,
required this.downloadService,
}) {
_init();
}
Expand All @@ -65,7 +68,7 @@ class EpisodeBloc extends Bloc {
await audioPlayerService.stop();
}

await podcastService.deleteDownload(episode!);
await downloadService.deleteDownload(episode!);

fetchDownloads(true);
});
Expand Down
14 changes: 1 addition & 13 deletions lib/bloc/podcast/podcast_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// found in the LICENSE file.

import 'package:anytime/bloc/bloc.dart';
import 'package:anytime/entities/downloadable.dart';
import 'package:anytime/entities/episode.dart';
import 'package:anytime/entities/feed.dart';
import 'package:anytime/entities/podcast.dart';
Expand Down Expand Up @@ -245,20 +244,9 @@ class PodcastBloc extends Bloc {
var episode = _episodes.firstWhereOrNull((ep) => ep.guid == e.guid);

if (episode != null) {
episode.downloadState = e.downloadState = DownloadState.queued;

_refresh();

var result = await downloadService.downloadEpisode(e);

// If there was an error downloading the episode, push an error state
// and then restore to none.
if (!result) {
episode.downloadState = e.downloadState = DownloadState.failed;
_refresh();
episode.downloadState = e.downloadState = DownloadState.none;
_refresh();
}
await downloadService.downloadEpisode(e);
}
});
}
Expand Down
5 changes: 4 additions & 1 deletion lib/services/audio/default_audio_player_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:anytime/entities/sleep.dart';
import 'package:anytime/entities/transcript.dart';
import 'package:anytime/repository/repository.dart';
import 'package:anytime/services/audio/audio_player_service.dart';
import 'package:anytime/services/download/download_service.dart';
import 'package:anytime/services/podcast/podcast_service.dart';
import 'package:anytime/services/settings/settings_service.dart';
import 'package:anytime/state/episode_state.dart';
Expand All @@ -37,6 +38,7 @@ class DefaultAudioPlayerService extends AudioPlayerService {
final log = Logger('DefaultAudioPlayerService');
final Repository repository;
final SettingsService settingsService;
final DownloadService downloadService;
final PodcastService podcastService;

late AudioHandler _audioHandler;
Expand Down Expand Up @@ -95,6 +97,7 @@ class DefaultAudioPlayerService extends AudioPlayerService {
required this.repository,
required this.settingsService,
required this.podcastService,
required this.downloadService,
}) {
AudioService.init(
builder: () => _DefaultAudioPlayerHandler(
Expand Down Expand Up @@ -535,7 +538,7 @@ class DefaultAudioPlayerService extends AudioPlayerService {
settingsService.deleteDownloadedPlayedEpisodes &&
_currentEpisode?.downloadState == DownloadState.downloaded && !sleepy
) {
await podcastService.deleteDownload(_currentEpisode!);
await downloadService.deleteDownload(_currentEpisode!);
}

_stopPositionTicker();
Expand Down
3 changes: 2 additions & 1 deletion lib/services/download/download_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
import 'package:anytime/entities/episode.dart';

abstract class DownloadService {
Future<bool> downloadEpisode(Episode episode);
Future<void> downloadEpisode(Episode episode);
Future<void> deleteDownload(Episode episode);

Future<Episode?> findEpisodeByTaskId(String taskId);

Expand Down
121 changes: 102 additions & 19 deletions lib/services/download/mobile_download_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ import 'package:anytime/repository/repository.dart';
import 'package:anytime/services/download/download_manager.dart';
import 'package:anytime/services/download/download_service.dart';
import 'package:anytime/services/podcast/podcast_service.dart';
import 'package:anytime/services/settings/settings_service.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:logging/logging.dart';
import 'package:mp3_info/mp3_info.dart';
import 'package:rxdart/rxdart.dart';
import 'package:synchronized/synchronized.dart';
import 'package:uuid/uuid.dart';

/// An implementation of a [DownloadService] that handles downloading
/// of episodes on mobile.
Expand All @@ -25,23 +29,40 @@ class MobileDownloadService extends DownloadService {

final log = Logger('MobileDownloadService');
final Repository repository;
final SettingsService settingsService;
final DownloadManager downloadManager;
final PodcastService podcastService;

MobileDownloadService({required this.repository, required this.downloadManager, required this.podcastService}) {
downloadManager.downloadProgress.pipe(downloadProgress);
downloadProgress.listen((progress) {
_updateDownloadProgress(progress);
});
late final StreamSubscription _downloadProgressSubscription;

/// Lock ensures we wait for task creation and local save
/// before handling subsequent [Download update events].
final _downloadLock = Lock();

MobileDownloadService({
required this.repository,
required this.downloadManager,
required this.settingsService,
required this.podcastService,
}) {
_downloadProgressSubscription = downloadManager.downloadProgress.listen(
(progress) async => await _downloadLock.synchronized(
() {
downloadProgress.add(progress);
_updateDownloadProgress(progress);
},
),
);
}

@override
void dispose() {
downloadManager.dispose();
_downloadProgressSubscription.cancel();
}

@override
Future<bool> downloadEpisode(Episode episode) async {
Future<void> downloadEpisode(Episode episode) async {
try {
final season = episode.season > 0 ? episode.season.toString() : '';
final epno = episode.episode > 0 ? episode.episode.toString() : '';
Expand Down Expand Up @@ -121,28 +142,90 @@ class MobileDownloadService extends DownloadService {
/// the URL before calling download and ensure it is https.
var url = await resolveUrl(episode.contentUrl!, forceHttps: true);

final taskId = await downloadManager.enqueueTask(url, downloadPath, filename);
await _downloadLock.synchronized(() async {
final taskId = await downloadManager.enqueueTask(url, downloadPath, filename!);

// Update the episode with download data
episode.filepath = episodePath;
episode.filename = filename;
episode.downloadTaskId = taskId;
episode.downloadState = DownloadState.downloading;
episode.downloadPercentage = 0;
// Update the episode with download data
episode.filepath = episodePath;
episode.filename = filename;
episode.downloadTaskId = taskId;
episode.downloadState = DownloadState.downloading;
episode.downloadPercentage = 0;

await repository.saveEpisode(episode);

return true;
await repository.saveEpisode(episode);
});
}
}

return false;
} catch (e, stack) {
log.warning('Episode download failed (${episode.title})', e, stack);
return false;
episode.filename = null;
episode.filepath = null;
episode.downloadTaskId = null;
episode.downloadPercentage = 0;
episode.downloadState = DownloadState.none;

await repository.saveEpisode(episode);

/// If there was an error downloading the episode, push an error state
/// and then restore to none.
///
/// If failure happens before download actual start, its [id] will be [null].
final downloadId = episode.downloadTaskId ?? const Uuid().v4();
downloadProgress
..add(DownloadProgress(
downloadId,
0,
DownloadState.failed,
))
..add(DownloadProgress(
downloadId,
0,
DownloadState.none,
));
}
}

@override
Future<void> deleteDownload(Episode episode) async => _downloadLock.synchronized(() async {
// If this episode is currently downloading, cancel the download first.
if (episode.downloadState == DownloadState.downloaded) {
if (settingsService.markDeletedEpisodesAsPlayed) {
episode.played = true;
}
} else if (episode.downloadState == DownloadState.downloading && episode.downloadPercentage! < 100) {
await FlutterDownloader.cancel(taskId: episode.downloadTaskId!);
}

episode.downloadTaskId = null;
episode.downloadPercentage = 0;
episode.position = 0;
episode.downloadState = DownloadState.none;

if (episode.transcriptId != null && episode.transcriptId! > 0) {
await repository.deleteTranscriptById(episode.transcriptId!);
}

await repository.saveEpisode(episode);

if (await hasStoragePermission()) {
final f = File.fromUri(Uri.file(await resolvePath(episode)));

log.fine('Deleting file ${f.path}');

if (await f.exists()) {
f.delete();
}
}

// downloadProgress.add(DownloadProgress(
// episode.downloadTaskId!,
// 0,
// DownloadState.none,
// ));

return;
});

@override
Future<Episode?> findEpisodeByTaskId(String taskId) {
return repository.findEpisodeByTaskId(taskId);
Expand Down
37 changes: 0 additions & 37 deletions lib/services/podcast/mobile_podcast_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import 'dart:io';
import 'package:anytime/api/podcast/podcast_api.dart';
import 'package:anytime/core/utils.dart';
import 'package:anytime/entities/chapter.dart';
import 'package:anytime/entities/downloadable.dart';
import 'package:anytime/entities/episode.dart';
import 'package:anytime/entities/funding.dart';
import 'package:anytime/entities/person.dart';
Expand All @@ -20,7 +19,6 @@ import 'package:anytime/state/episode_state.dart';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart';
Expand Down Expand Up @@ -507,41 +505,6 @@ class MobilePodcastService extends PodcastService {
return repository.findAllEpisodes();
}

@override
Future<void> deleteDownload(Episode episode) async {
// If this episode is currently downloading, cancel the download first.
if (episode.downloadState == DownloadState.downloaded) {
if (settingsService.markDeletedEpisodesAsPlayed) {
episode.played = true;
}
} else if (episode.downloadState == DownloadState.downloading && episode.downloadPercentage! < 100) {
await FlutterDownloader.cancel(taskId: episode.downloadTaskId!);
}

episode.downloadTaskId = null;
episode.downloadPercentage = 0;
episode.position = 0;
episode.downloadState = DownloadState.none;

if (episode.transcriptId != null && episode.transcriptId! > 0) {
await repository.deleteTranscriptById(episode.transcriptId!);
}

await repository.saveEpisode(episode);

if (await hasStoragePermission()) {
final f = File.fromUri(Uri.file(await resolvePath(episode)));

log.fine('Deleting file ${f.path}');

if (await f.exists()) {
f.delete();
}
}

return;
}

@override
Future<void> toggleEpisodePlayed(Episode episode) async {
episode.played = !episode.played;
Expand Down
2 changes: 0 additions & 2 deletions lib/services/podcast/podcast_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,6 @@ abstract class PodcastService {

Future<Transcript> loadTranscriptByUrl({required TranscriptUrl transcriptUrl});

Future<void> deleteDownload(Episode episode);

Future<void> toggleEpisodePlayed(Episode episode);

Future<List<Podcast>> subscriptions();
Expand Down
Loading