mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 15:35:17 +00:00
feat: add download multi tracks support for mobile platform
This commit is contained in:
parent
9edcf537f8
commit
0476bf7cee
4
.fvm/fvm_config.json
Normal file
4
.fvm/fvm_config.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"flutterSdkVersion": "3.0.5",
|
||||
"flavors": {}
|
||||
}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -75,3 +75,4 @@ dist
|
||||
appimage-build
|
||||
|
||||
android/key.properties
|
||||
.fvm/flutter_sdk
|
@ -50,7 +50,7 @@ android {
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "oss.krtirtho.spotube"
|
||||
minSdkVersion flutter.minSdkVersion
|
||||
minSdkVersion 19
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
@ -15,7 +15,7 @@
|
||||
</queries>
|
||||
|
||||
<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
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
@ -28,6 +28,10 @@
|
||||
|
||||
</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">
|
||||
<intent-filter>
|
||||
<action android:name="android.media.browse.MediaBrowserService" />
|
||||
|
@ -2,7 +2,9 @@ import 'dart:convert';
|
||||
|
||||
import 'package:audio_service/audio_service.dart';
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
@ -26,8 +28,8 @@ void main() async {
|
||||
Hive.registerAdapter(CacheTrackAdapter());
|
||||
Hive.registerAdapter(CacheTrackEngagementAdapter());
|
||||
Hive.registerAdapter(CacheTrackSkipSegmentAdapter());
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
if (kIsDesktop) {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
doWhenWindowReady(() async {
|
||||
final localStorage = await SharedPreferences.getInstance();
|
||||
final rawSize = localStorage.getString(LocalStorageKeys.windowSizeInfo);
|
||||
@ -44,6 +46,11 @@ void main() async {
|
||||
}
|
||||
appWindow.show();
|
||||
});
|
||||
} else {
|
||||
await FlutterDownloader.initialize(
|
||||
debug: kDebugMode,
|
||||
ignoreSsl: true,
|
||||
);
|
||||
}
|
||||
MobileAudioService? audioServiceHandler;
|
||||
runApp(ProviderScope(
|
||||
@ -102,8 +109,13 @@ class _SpotubeState extends ConsumerState<Spotube> with WidgetsBindingObserver {
|
||||
super.initState();
|
||||
SharedPreferences.getInstance().then(((value) => localStorage = value));
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
FlutterDownloader.registerCallback(downloadCallback);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
static void downloadCallback(
|
||||
String id, DownloadTaskStatus status, int progress) {}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
|
@ -2,12 +2,14 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:queue/queue.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:spotube/models/SpotubeTrack.dart';
|
||||
import 'package:spotube/provider/UserPreferences.dart';
|
||||
import 'package:spotube/provider/YouTube.dart';
|
||||
import 'package:spotube/utils/platform.dart';
|
||||
import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
|
||||
Queue _queueInstance = Queue(delay: const Duration(seconds: 5));
|
||||
@ -27,44 +29,64 @@ class Downloader with ChangeNotifier {
|
||||
int currentlyRunning = 0;
|
||||
Set<String> inQueue = {};
|
||||
|
||||
void addToQueue(SpotubeTrack track) {
|
||||
if (inQueue.contains(track.id!)) return;
|
||||
void addToQueue(SpotubeTrack track) async {
|
||||
currentlyRunning++;
|
||||
inQueue.add(track.id!);
|
||||
notifyListeners();
|
||||
_queue.add(() async {
|
||||
try {
|
||||
final file =
|
||||
File(path.join(downloadPath, '${track.ytTrack.title}.mp3'));
|
||||
if (file.existsSync() && await onFileExists?.call(track) != true) {
|
||||
return;
|
||||
}
|
||||
file.createSync(recursive: true);
|
||||
StreamManifest manifest =
|
||||
await yt.videos.streamsClient.getManifest(track.ytTrack.url);
|
||||
final audioStream = yt.videos.streamsClient
|
||||
.get(
|
||||
manifest.audioOnly
|
||||
.where((audio) => audio.codec.mimeType == "audio/mp4")
|
||||
.withHighestBitrate(),
|
||||
)
|
||||
.asBroadcastStream();
|
||||
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 {
|
||||
try {
|
||||
final file = File(path.join(downloadPath, filename));
|
||||
if (file.existsSync() && await onFileExists?.call(track) != true) {
|
||||
return;
|
||||
}
|
||||
file.createSync(recursive: true);
|
||||
StreamManifest manifest =
|
||||
await yt.videos.streamsClient.getManifest(track.ytTrack.url);
|
||||
final audioStream = yt.videos.streamsClient
|
||||
.get(
|
||||
manifest.audioOnly
|
||||
.where((audio) => audio.codec.mimeType == "audio/mp4")
|
||||
.withHighestBitrate(),
|
||||
)
|
||||
.asBroadcastStream();
|
||||
|
||||
IOSink outputFileStream = file.openWrite();
|
||||
await audioStream.pipe(outputFileStream);
|
||||
await outputFileStream.flush();
|
||||
} finally {
|
||||
currentlyRunning--;
|
||||
inQueue.remove(track.id);
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
IOSink outputFileStream = file.openWrite();
|
||||
await audioStream.pipe(outputFileStream);
|
||||
await outputFileStream.flush();
|
||||
} finally {
|
||||
currentlyRunning--;
|
||||
inQueue.remove(track.id);
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
_queue.cancel();
|
||||
_queueInstance = Queue();
|
||||
_queue = _queueInstance;
|
||||
cancelAll() {
|
||||
if (kIsMobile) {
|
||||
FlutterDownloader.cancelAll();
|
||||
} else {
|
||||
_queue.cancel();
|
||||
_queueInstance = Queue();
|
||||
_queue = _queueInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user