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
android/key.properties
.fvm/flutter_sdk

View File

@ -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

View File

@ -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" />

View File

@ -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);

View File

@ -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;
}
}
}