Skip to content

Commit

Permalink
feat: Fixed / Improved downloader handling
Browse files Browse the repository at this point in the history
  • Loading branch information
khaled-0 committed Oct 28, 2024
1 parent 2c98776 commit 8f8ca66
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 51 deletions.
10 changes: 0 additions & 10 deletions lib/app/extensions.dart

This file was deleted.

11 changes: 8 additions & 3 deletions lib/app/more/downloads/active_downloads_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:background_downloader/background_downloader.dart';
import 'package:flutter/material.dart';
import 'package:tube_sync/app/more/downloads/download_entry_builder.dart';
import 'package:tube_sync/main.dart';
import 'package:tube_sync/provider/media_provider.dart';

class ActiveDownloadsScreen extends StatefulWidget {
const ActiveDownloadsScreen({super.key});
Expand Down Expand Up @@ -67,6 +68,8 @@ class _ActiveDownloadsScreenState extends State<ActiveDownloadsScreen> {
}

Future<void> cancelAll() async {
FileDownloader().taskQueues.forEach(FileDownloader().removeTaskQueue);
MediaProvider().abortQueueing();
Iterable<TaskRecord> records = await FileDownloader().database.allRecords();

await FileDownloader().cancelTasksWithIds(
Expand All @@ -84,6 +87,7 @@ class _ActiveDownloadsScreenState extends State<ActiveDownloadsScreen> {
future: FileDownloader().database.allRecords(),
initialData: [],
builder: (context, snapshot) {
if (snapshot.hasError) return const SizedBox();
if (snapshot.requireData.isEmpty) return const SizedBox();
return FloatingActionButton.extended(
icon: Icon(Icons.clear_all_rounded),
Expand All @@ -96,10 +100,11 @@ class _ActiveDownloadsScreenState extends State<ActiveDownloadsScreen> {
future: FileDownloader().database.allRecords(),
initialData: [],
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
if (snapshot.requireData.isEmpty) {
return Center(
child: Text("No Active Downloads!"),
);
return Center(child: Text("No Active Downloads!"));
}

return ListView.builder(
Expand Down
7 changes: 6 additions & 1 deletion lib/app/more/downloads/download_entry_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'dart:math';

import 'package:background_downloader/background_downloader.dart';
import 'package:flutter/material.dart';
import 'package:tube_sync/extensions.dart';

class DownloadEntryBuilder extends StatelessWidget {
const DownloadEntryBuilder({
Expand All @@ -26,7 +27,11 @@ class DownloadEntryBuilder extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [Text(getFileSizeString(bytes: entry.expectedFileSize))],
children: [
Text(entry.status.name.normalizeCamelCase().toCapitalCase()),
Text(" \u2022 "),
Text(getFileSizeString(bytes: entry.expectedFileSize))
],
),
LinearProgressIndicator(value: entry.progress)
],
Expand Down
2 changes: 1 addition & 1 deletion lib/app/player/expanded_player_sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'package:audioplayers/audioplayers.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tube_sync/app/extensions.dart';
import 'package:tube_sync/extensions.dart';
import 'package:tube_sync/model/media.dart';
import 'package:tube_sync/model/playlist.dart';
import 'package:tube_sync/provider/player_provider.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/app/playlist/media_entry_builder.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tube_sync/app/extensions.dart';
import 'package:tube_sync/extensions.dart';
import 'package:tube_sync/app/playlist/media_menu_sheet.dart';
import 'package:tube_sync/model/media.dart';
import 'package:tube_sync/provider/playlist_provider.dart';
Expand Down
7 changes: 3 additions & 4 deletions lib/app/playlist/media_menu_sheet.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tube_sync/app/more/downloads/active_downloads_screen.dart';
import 'package:tube_sync/model/media.dart';
import 'package:tube_sync/provider/playlist_provider.dart';
import 'package:tube_sync/provider/media_provider.dart';

class MediaMenuSheet extends StatelessWidget {
final Media media;
Expand Down Expand Up @@ -32,7 +31,7 @@ class MediaMenuSheet extends StatelessWidget {
if (media.downloaded != true)
ListTile(
onTap: () {
// context.read<PlaylistProvider>().downloadMedia(media);
MediaProvider().download(media);
Navigator.pop(context);
ActiveDownloadsScreen.showEnqueuedSnackbar(context);
},
Expand All @@ -42,7 +41,7 @@ class MediaMenuSheet extends StatelessWidget {
if (media.downloaded == true)
ListTile(
onTap: () {
// context.read<PlaylistProvider>().deleteMedia(media);
MediaProvider().delete(media);
Navigator.pop(context);
},
leading: Icon(Icons.delete_rounded),
Expand Down
7 changes: 6 additions & 1 deletion lib/app/playlist/playlist_menu_sheet.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tube_sync/app/more/downloads/active_downloads_screen.dart';
import 'package:tube_sync/provider/media_provider.dart';
import 'package:tube_sync/provider/playlist_provider.dart';

class PlaylistMenuSheet extends StatelessWidget {
const PlaylistMenuSheet({super.key});
Expand All @@ -26,7 +29,9 @@ class PlaylistMenuSheet extends StatelessWidget {
),
ListTile(
onTap: () {
// context.read<PlaylistProvider>().downloadAll();
MediaProvider().downloadAll(
context.read<PlaylistProvider>().medias,
);
ActiveDownloadsScreen.showEnqueuedSnackbar(context);
Navigator.pop(context);
},
Expand Down
24 changes: 24 additions & 0 deletions lib/extensions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
extension DurationExtensions on Duration? {
String formatHHMM() {
if (this == null) return "??:??";
String twoDigits(int n) => n.toString().padLeft(2, '0');
final String twoDigitMinutes = twoDigits(this!.inMinutes.remainder(60));
final String twoDigitSeconds = twoDigits(this!.inSeconds.remainder(60));
final hour = twoDigits(this!.inHours);
return " ${hour == '00' ? '' : '$hour:'}$twoDigitMinutes:$twoDigitSeconds ";
}
}

extension StringExtensions on String {
/// Converts SUSSY BAKA, SuSSy bAKA sussY BaKA etc to Sussy Baka
String toCapitalCase() => splitMapJoin(" ", onNonMatch: (n) {
if (n.length <= 2) return n.toUpperCase();
return "${n[0].toUpperCase()}${n.substring(1).toLowerCase()}";
});

/// sussyBaka -> sussy Baka
String normalizeCamelCase() => replaceAllMapped(
RegExp(r"([A-Z]){1,3}"),
(match) => " ${match[0]}",
);
}
7 changes: 7 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:tube_sync/app/more/downloads/active_downloads_screen.dart';
import 'package:tube_sync/model/media.dart';
import 'package:tube_sync/model/playlist.dart';
import 'package:tube_sync/model/preferences.dart';
import 'package:tube_sync/provider/media_provider.dart';

final rootNavigator = GlobalKey<NavigatorState>();

Expand All @@ -26,6 +27,11 @@ void main() async {
Preference.materialYou,
);

// Limit concurrent downloads
await FileDownloader().configure(
globalConfig: (Config.holdingQueue, (3, 3, 3)),
);

// Background downloader notifications
FileDownloader().configureNotification(
running: TaskNotification('Downloading', '{displayName}'),
Expand All @@ -41,6 +47,7 @@ void main() async {

// Using the database to track Tasks
FileDownloader().trackTasks();
await MediaProvider().init();

runApp(
ValueListenableBuilder(
Expand Down
95 changes: 65 additions & 30 deletions lib/provider/media_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,89 @@ import 'package:tube_sync/model/media.dart';
import 'package:youtube_explode_dart/youtube_explode_dart.dart' as yt;

class MediaProvider {
MediaProvider._();

static final MediaProvider _instance = MediaProvider._();

factory MediaProvider() => _instance;

Future<void> init() async {
final dir = await getApplicationCacheDirectory();
mediaFileDir = dir.path + Platform.pathSeparator;
}

late final String mediaFileDir;
final _ytClient = yt.YoutubeExplode().videos.streamsClient;

bool _abortQueueing = false;

File mediaFile(Media media) => File(mediaFileDir + media.id);

Future<Source?> getMedia(Media media) async {
// Try from offline
final downloaded = File(await mediaFilePath(media));
final downloaded = mediaFile(media);
if (downloaded.existsSync()) return DeviceFileSource(downloaded.path);

if (!await hasInternet) return null;

final videoManifest = await _ytClient.getManifest(media.id);
final streamUri = videoManifest.audioOnly.withHighestBitrate().url;
final source = UrlSource(streamUri.toString());

return source;
return UrlSource(streamUri.toString());
}

static Future<String> mediaFilePath(Media media) async {
// return "/home/khaled/.cache/tubesync.app/${media.id}";
final dir = await getApplicationCacheDirectory();
return dir.path + Platform.pathSeparator + media.id;
}
Future<void> download(Media media) async {
try {
final manifest = await _ytClient.getManifest(media.id);
final url = manifest.audioOnly.withHighestBitrate().url.toString();

static Future<void> download(Media media) async {
final ytClient = yt.YoutubeExplode().videos.streamsClient;
final manifest = await ytClient.getManifest(media.id);
final url = manifest.audioOnly.withHighestBitrate().url.toString();
final directory = await mediaFilePath(media);

final task = DownloadTask(
url: url,
displayName: media.title,
directory: directory.replaceFirst(media.id, ''),
filename: media.id,
baseDirectory: BaseDirectory.root,
updates: Updates.statusAndProgress,
);
await FileDownloader().enqueue(task);
await FileDownloader().database.recordForId(task.taskId);
final task = DownloadTask(
url: url,
displayName: media.title,
directory: mediaFileDir,
filename: media.id,
baseDirectory: BaseDirectory.root,
updates: Updates.statusAndProgress,
);

await FileDownloader().enqueue(task);
} catch (_) {
//TODO Error
}
}

static Future<bool> isDownloaded(Media media) async {
return File(await mediaFilePath(media)).exists();
Future<void> downloadAll(List<Media> medias) async {
_abortQueueing = false;
for (final media in medias) {
try {
if (mediaFile(media).existsSync()) continue;

final manifest = await _ytClient.getManifest(media.id);
final url = manifest.audioOnly.withHighestBitrate().url.toString();

if (_abortQueueing) break;

FileDownloader().enqueue(DownloadTask(
url: url,
displayName: media.title,
directory: mediaFileDir,
filename: media.id,
baseDirectory: BaseDirectory.root,
updates: Updates.statusAndProgress,
));
} catch (_) {
// TODO Error
}
}
}

static Future<void> delete(Media media) async {
final file = File(await mediaFilePath(media));
void abortQueueing() => _abortQueueing = true;

Future<bool> isDownloaded(Media media) async => mediaFile(media).exists();

Future<void> delete(Media media) async {
final file = mediaFile(media);
if (file.existsSync()) await file.delete();
}

Future<bool> get hasInternet => InternetConnection().hasInternetAccess;
static Future<bool> get hasInternet => InternetConnection().hasInternetAccess;
}

0 comments on commit 8f8ca66

Please sign in to comment.