feat: add download multi tracks support for mobile platform

This commit is contained in:
Kingkor Roy Tirtho 2022-08-14 15:44:54 +06:00
parent 9edcf537f8
commit 0476bf7cee
6 changed files with 78 additions and 35 deletions

4
.fvm/fvm_config.json Normal file
View File

@ -0,0 +1,4 @@
{
"flutterSdkVersion": "3.0.5",
"flavors": {}
}

1
.gitignore vendored
View File

@ -75,3 +75,4 @@ dist
appimage-build appimage-build
android/key.properties android/key.properties
.fvm/flutter_sdk

View File

@ -50,7 +50,7 @@ android {
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "oss.krtirtho.spotube" applicationId "oss.krtirtho.spotube"
minSdkVersion flutter.minSdkVersion minSdkVersion 19
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName

View File

@ -15,7 +15,7 @@
</queries> </queries>
<application android:label="Spotube" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true" android:requestLegacyExternalStorage="true"> <application android:label="Spotube" android:name="${applicationName}" android:icon="@mipmap/ic_launcher" android:usesCleartextTraffic="true" android:requestLegacyExternalStorage="true">
<activity android:name="com.ryanheise.audioservice.AudioServiceActivity" android:exported="true" 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="com.ryanheise.audioservice.AudioServiceActivity" android:exported="true" 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">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues
@ -28,6 +28,10 @@
</activity> </activity>
<provider android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider" android:authorities="${applicationId}.flutter_downloader.provider" android:exported="false" android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" />
</provider>
<service android:name="com.ryanheise.audioservice.AudioService" android:exported="false"> <service android:name="com.ryanheise.audioservice.AudioService" android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.media.browse.MediaBrowserService" /> <action android:name="android.media.browse.MediaBrowserService" />

View File

@ -2,7 +2,9 @@ import 'dart:convert';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
@ -26,8 +28,8 @@ void main() async {
Hive.registerAdapter(CacheTrackAdapter()); Hive.registerAdapter(CacheTrackAdapter());
Hive.registerAdapter(CacheTrackEngagementAdapter()); Hive.registerAdapter(CacheTrackEngagementAdapter());
Hive.registerAdapter(CacheTrackSkipSegmentAdapter()); Hive.registerAdapter(CacheTrackSkipSegmentAdapter());
if (kIsDesktop) {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
if (kIsDesktop) {
doWhenWindowReady(() async { doWhenWindowReady(() async {
final localStorage = await SharedPreferences.getInstance(); final localStorage = await SharedPreferences.getInstance();
final rawSize = localStorage.getString(LocalStorageKeys.windowSizeInfo); final rawSize = localStorage.getString(LocalStorageKeys.windowSizeInfo);
@ -44,6 +46,11 @@ void main() async {
} }
appWindow.show(); appWindow.show();
}); });
} else {
await FlutterDownloader.initialize(
debug: kDebugMode,
ignoreSsl: true,
);
} }
MobileAudioService? audioServiceHandler; MobileAudioService? audioServiceHandler;
runApp(ProviderScope( runApp(ProviderScope(
@ -102,8 +109,13 @@ class _SpotubeState extends ConsumerState<Spotube> with WidgetsBindingObserver {
super.initState(); super.initState();
SharedPreferences.getInstance().then(((value) => localStorage = value)); SharedPreferences.getInstance().then(((value) => localStorage = value));
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
FlutterDownloader.registerCallback(downloadCallback);
} }
@pragma('vm:entry-point')
static void downloadCallback(
String id, DownloadTaskStatus status, int progress) {}
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);

View File

@ -2,12 +2,14 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:queue/queue.dart'; import 'package:queue/queue.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:spotube/models/SpotubeTrack.dart'; import 'package:spotube/models/SpotubeTrack.dart';
import 'package:spotube/provider/UserPreferences.dart'; import 'package:spotube/provider/UserPreferences.dart';
import 'package:spotube/provider/YouTube.dart'; import 'package:spotube/provider/YouTube.dart';
import 'package:spotube/utils/platform.dart';
import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart';
Queue _queueInstance = Queue(delay: const Duration(seconds: 5)); Queue _queueInstance = Queue(delay: const Duration(seconds: 5));
@ -27,15 +29,30 @@ class Downloader with ChangeNotifier {
int currentlyRunning = 0; int currentlyRunning = 0;
Set<String> inQueue = {}; Set<String> inQueue = {};
void addToQueue(SpotubeTrack track) { void addToQueue(SpotubeTrack track) async {
if (inQueue.contains(track.id!)) return;
currentlyRunning++; currentlyRunning++;
inQueue.add(track.id!); inQueue.add(track.id!);
notifyListeners(); notifyListeners();
final filename = '${track.ytTrack.title}.mp3';
if (kIsMobile) {
final url =
((await yt.videos.streamsClient.getManifest(track.ytTrack.url)))
.audioOnly
.where((audio) => audio.codec.mimeType == "audio/mp4")
.withHighestBitrate()
.url;
await FlutterDownloader.enqueue(
savedDir: downloadPath,
url: url.toString(),
fileName: filename,
openFileFromNotification: true,
showNotification: true,
);
} else {
if (inQueue.contains(track.id!)) return;
_queue.add(() async { _queue.add(() async {
try { try {
final file = final file = File(path.join(downloadPath, filename));
File(path.join(downloadPath, '${track.ytTrack.title}.mp3'));
if (file.existsSync() && await onFileExists?.call(track) != true) { if (file.existsSync() && await onFileExists?.call(track) != true) {
return; return;
} }
@ -60,12 +77,17 @@ class Downloader with ChangeNotifier {
} }
}); });
} }
}
cancel() { cancelAll() {
if (kIsMobile) {
FlutterDownloader.cancelAll();
} else {
_queue.cancel(); _queue.cancel();
_queueInstance = Queue(); _queueInstance = Queue();
_queue = _queueInstance; _queue = _queueInstance;
} }
}
} }
final downloaderProvider = ChangeNotifierProvider( final downloaderProvider = ChangeNotifierProvider(