mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-06 07:29:42 +00:00
Compare commits
17 Commits
37e60fb4a6
...
4aaeb1b145
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aaeb1b145 | ||
|
|
d843ce9ede | ||
|
|
6884a131c9 | ||
|
|
bb6f4bd57b | ||
|
|
cab09e00ce | ||
|
|
24f186e9fd | ||
|
|
a8f70f201e | ||
|
|
b9c6c98e38 | ||
|
|
a65846d15e | ||
|
|
ba27dc70e4 | ||
|
|
723b6b1f38 | ||
|
|
464666c01a | ||
|
|
0e58cd0e99 | ||
|
|
d4f70f56e4 | ||
|
|
8c1337d1fc | ||
|
|
94e704087f | ||
|
|
8e287ab1e5 |
@ -36,7 +36,7 @@ android {
|
||||
|
||||
compileSdkVersion 36
|
||||
|
||||
ndkVersion = "27.0.12077973"
|
||||
ndkVersion = "29.0.14206865"
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
|
||||
Binary file not shown.
@ -19,6 +19,7 @@ import 'package:spotube/services/kv_store/kv_store.dart';
|
||||
import 'package:flutter/widgets.dart' hide Table, Key, View;
|
||||
import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
import 'package:spotube/services/youtube_engine/newpipe_engine.dart';
|
||||
import 'package:spotube/services/youtube_engine/youtube_explode_engine.dart';
|
||||
import 'package:spotube/services/youtube_engine/yt_dlp_engine.dart';
|
||||
@ -200,26 +201,41 @@ class AppDatabase extends _$AppDatabase {
|
||||
});
|
||||
},
|
||||
from8To9: (m, schema) async {
|
||||
await m.renameTable(schema.pluginsTable, "metadata_plugins_table");
|
||||
await m.renameColumn(
|
||||
schema.pluginsTable,
|
||||
"selected",
|
||||
pluginsTable.selectedForMetadata,
|
||||
);
|
||||
await m.addColumn(
|
||||
schema.pluginsTable,
|
||||
pluginsTable.selectedForAudioSource,
|
||||
);
|
||||
await m
|
||||
.renameTable(schema.pluginsTable, "metadata_plugins_table")
|
||||
.catchError((e, stack) => AppLogger.reportError(e, stack));
|
||||
await m
|
||||
.renameColumn(
|
||||
schema.pluginsTable,
|
||||
"selected",
|
||||
pluginsTable.selectedForMetadata,
|
||||
)
|
||||
.catchError((e, stack) => AppLogger.reportError(e, stack));
|
||||
await m
|
||||
.addColumn(
|
||||
schema.pluginsTable,
|
||||
pluginsTable.selectedForAudioSource,
|
||||
)
|
||||
.catchError((e, stack) => AppLogger.reportError(e, stack));
|
||||
},
|
||||
from9To10: (m, schema) async {
|
||||
await m.dropColumn(schema.preferencesTable, "piped_instance");
|
||||
await m.dropColumn(schema.preferencesTable, "invidious_instance");
|
||||
await m.addColumn(
|
||||
schema.sourceMatchTable,
|
||||
sourceMatchTable.sourceInfo,
|
||||
);
|
||||
await customStatement("DROP INDEX IF EXISTS uniq_track_match;");
|
||||
await m.dropColumn(schema.sourceMatchTable, "source_id");
|
||||
await m
|
||||
.dropColumn(schema.preferencesTable, "piped_instance")
|
||||
.catchError((e, stack) => AppLogger.reportError(e, stack));
|
||||
await m
|
||||
.dropColumn(schema.preferencesTable, "invidious_instance")
|
||||
.catchError((e, stack) => AppLogger.reportError(e, stack));
|
||||
await m
|
||||
.addColumn(
|
||||
schema.sourceMatchTable,
|
||||
sourceMatchTable.sourceInfo,
|
||||
)
|
||||
.catchError((e, stack) => AppLogger.reportError(e, stack));
|
||||
await customStatement("DROP INDEX IF EXISTS uniq_track_match;")
|
||||
.catchError((e, stack) => AppLogger.reportError(e, stack));
|
||||
await m
|
||||
.dropColumn(schema.sourceMatchTable, "source_id")
|
||||
.catchError((e, stack) => AppLogger.reportError(e, stack));
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@ -198,7 +198,7 @@ class LocalLibraryPage extends HookConsumerWidget {
|
||||
),
|
||||
);
|
||||
|
||||
if (accepted ?? false) return;
|
||||
if (accepted != true) return;
|
||||
|
||||
final cacheDir = Directory(
|
||||
await UserPreferencesNotifier.getMusicCacheDir(),
|
||||
@ -207,6 +207,8 @@ class LocalLibraryPage extends HookConsumerWidget {
|
||||
if (cacheDir.existsSync()) {
|
||||
await cacheDir.delete(recursive: true);
|
||||
}
|
||||
|
||||
ref.invalidate(localTracksProvider);
|
||||
},
|
||||
),
|
||||
IconButton.outline(
|
||||
|
||||
@ -51,15 +51,17 @@ class AudioPlayerState with _$AudioPlayerState {
|
||||
}
|
||||
|
||||
bool containsTrack(SpotubeTrackObject track) {
|
||||
return tracks.any(
|
||||
(t) => t is SpotubeLocalTrackObject && track is SpotubeLocalTrackObject
|
||||
? t.path == track.path
|
||||
: t.id == track.id,
|
||||
);
|
||||
return tracks.isNotEmpty &&
|
||||
tracks.any(
|
||||
(t) =>
|
||||
t is SpotubeLocalTrackObject && track is SpotubeLocalTrackObject
|
||||
? t.path == track.path
|
||||
: t.id == track.id,
|
||||
);
|
||||
}
|
||||
|
||||
bool containsTracks(List<SpotubeTrackObject> tracks) {
|
||||
return tracks.every(containsTrack);
|
||||
return this.tracks.isNotEmpty && tracks.every(containsTrack);
|
||||
}
|
||||
|
||||
bool containsCollection(String collectionId) {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
@ -124,8 +125,7 @@ class ServerPlaybackRoutes {
|
||||
return res;
|
||||
}
|
||||
|
||||
Future<({dio_lib.Response<Uint8List> response, Uint8List? bytes})>
|
||||
streamTrack(
|
||||
Future<dio_lib.Response> streamTrack(
|
||||
Request request,
|
||||
SourcedTrack track,
|
||||
Map<String, dynamic> headers,
|
||||
@ -141,30 +141,29 @@ class ServerPlaybackRoutes {
|
||||
final bytes = await trackCacheFile.readAsBytes();
|
||||
final cachedFileLength = bytes.length;
|
||||
|
||||
return (
|
||||
response: dio_lib.Response<Uint8List>(
|
||||
statusCode: 200,
|
||||
headers: Headers.fromMap({
|
||||
"content-type": ["audio/${track.qualityPreset!.name}"],
|
||||
"content-length": ["$cachedFileLength"],
|
||||
"accept-ranges": ["bytes"],
|
||||
"content-range": ["bytes 0-$cachedFileLength/$cachedFileLength"],
|
||||
}),
|
||||
requestOptions: RequestOptions(path: request.requestedUri.toString()),
|
||||
),
|
||||
bytes: bytes,
|
||||
return dio_lib.Response<Uint8List>(
|
||||
statusCode: 200,
|
||||
headers: Headers.fromMap({
|
||||
"content-type": ["audio/${track.qualityPreset!.name}"],
|
||||
"content-length": ["${cachedFileLength - 1}"],
|
||||
"accept-ranges": ["bytes"],
|
||||
"content-range": [
|
||||
"bytes 0-${cachedFileLength - 1}/$cachedFileLength"
|
||||
],
|
||||
"connection": ["close"],
|
||||
}),
|
||||
requestOptions: RequestOptions(path: request.requestedUri.toString()),
|
||||
data: bytes,
|
||||
);
|
||||
}
|
||||
|
||||
final trackPartialCacheFile = File("${trackCacheFile.path}.part");
|
||||
|
||||
String url = track.url ??
|
||||
await ref
|
||||
.read(sourcedTrackProvider(track.query).notifier)
|
||||
.swapWithNextSibling()
|
||||
.then((track) => track.url!);
|
||||
|
||||
var options = Options(
|
||||
final options = Options(
|
||||
headers: {
|
||||
...headers,
|
||||
"user-agent": _randomUserAgent,
|
||||
@ -172,12 +171,15 @@ class ServerPlaybackRoutes {
|
||||
"Connection": "keep-alive",
|
||||
"host": Uri.parse(url).host,
|
||||
},
|
||||
responseType: ResponseType.bytes,
|
||||
responseType: ResponseType.stream,
|
||||
validateStatus: (status) => status! < 400,
|
||||
);
|
||||
|
||||
final contentLengthRes = await Future<dio_lib.Response?>.value(
|
||||
dio.head(url, options: options),
|
||||
dio.head(
|
||||
url,
|
||||
options: options.copyWith(responseType: ResponseType.bytes),
|
||||
),
|
||||
).catchError((e, stack) async {
|
||||
AppLogger.reportError(e, stack);
|
||||
|
||||
@ -193,39 +195,19 @@ class ServerPlaybackRoutes {
|
||||
// Redirect to m3u8 link directly as it handles range requests internally
|
||||
if (contentLengthRes?.headers.value("content-type") ==
|
||||
"application/vnd.apple.mpegurl") {
|
||||
return (
|
||||
response: dio_lib.Response<Uint8List>(
|
||||
statusCode: 301,
|
||||
statusMessage: "M3U8 Redirect",
|
||||
headers: Headers.fromMap({
|
||||
"location": [url],
|
||||
"content-type": ["application/vnd.apple.mpegurl"],
|
||||
}),
|
||||
requestOptions: RequestOptions(path: request.requestedUri.toString()),
|
||||
isRedirect: true,
|
||||
),
|
||||
bytes: null,
|
||||
return dio_lib.Response<Uint8List>(
|
||||
statusCode: 301,
|
||||
statusMessage: "M3U8 Redirect",
|
||||
headers: Headers.fromMap({
|
||||
"location": [url],
|
||||
"content-type": ["application/vnd.apple.mpegurl"],
|
||||
}),
|
||||
requestOptions: RequestOptions(path: request.requestedUri.toString()),
|
||||
isRedirect: true,
|
||||
);
|
||||
}
|
||||
|
||||
if (headers["range"] == "bytes=0-" &&
|
||||
track.qualityPreset is SpotubeAudioSourceContainerPresetLossless) {
|
||||
const bufferSize = 6 * 1024 * 1024; // 6MB for lossless
|
||||
|
||||
final endRange = min(
|
||||
bufferSize,
|
||||
int.parse(contentLengthRes?.headers.value("content-length") ?? "0"),
|
||||
);
|
||||
|
||||
options = options.copyWith(
|
||||
headers: {
|
||||
...?options.headers,
|
||||
"range": "bytes=0-$endRange",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final res = await dio.get<Uint8List>(url, options: options);
|
||||
final res = await dio.get<ResponseBody>(url, options: options);
|
||||
|
||||
AppLogger.log.i(
|
||||
"Response for track: ${track.query.name}\n"
|
||||
@ -233,66 +215,64 @@ class ServerPlaybackRoutes {
|
||||
"Headers: ${res.headers.map}",
|
||||
);
|
||||
|
||||
final bytes = res.data;
|
||||
|
||||
if (bytes == null || !userPreferences.cacheMusic) {
|
||||
return (response: res, bytes: bytes);
|
||||
if (!userPreferences.cacheMusic) {
|
||||
return res;
|
||||
}
|
||||
|
||||
final contentRange =
|
||||
ContentRangeHeader.parse(res.headers.value("content-range") ?? "");
|
||||
final resStream = res.data!.stream.asBroadcastStream();
|
||||
|
||||
final trackPartialCacheFile = File("${trackCacheFile.path}.part");
|
||||
if (!await trackPartialCacheFile.exists()) {
|
||||
await trackPartialCacheFile.create(recursive: true);
|
||||
}
|
||||
|
||||
// Write the stream to the file based on the range
|
||||
final partialCacheFile =
|
||||
await trackPartialCacheFile.open(mode: FileMode.writeOnlyAppend);
|
||||
int fileLength = 0;
|
||||
try {
|
||||
await partialCacheFile.setPosition(contentRange.start);
|
||||
await partialCacheFile.writeFrom(bytes);
|
||||
fileLength = await partialCacheFile.length();
|
||||
} finally {
|
||||
await partialCacheFile.close();
|
||||
}
|
||||
final partialCacheFileSink =
|
||||
trackPartialCacheFile.openWrite(mode: FileMode.writeOnlyAppend);
|
||||
final contentRange = res.headers.value("content-range") != null
|
||||
? ContentRangeHeader.parse(res.headers.value("content-range") ?? "")
|
||||
: ContentRangeHeader(0, 0, 0);
|
||||
|
||||
if (fileLength == contentRange.total) {
|
||||
await trackPartialCacheFile.rename(trackCacheFile.path);
|
||||
}
|
||||
resStream.listen(
|
||||
(data) {
|
||||
partialCacheFileSink.add(data);
|
||||
},
|
||||
onError: (e, stack) {
|
||||
partialCacheFileSink.close();
|
||||
},
|
||||
onDone: () async {
|
||||
await partialCacheFileSink.close();
|
||||
|
||||
if (contentRange.total == fileLength &&
|
||||
track.qualityPreset!.getFileExtension() != "weba") {
|
||||
final playlistTrack = playlist.tracks.firstWhereOrNull(
|
||||
(element) => element.id == track.query.id,
|
||||
);
|
||||
if (playlistTrack == null) {
|
||||
AppLogger.log.e(
|
||||
"Track ${track.query.id} not found in playlist, cannot write metadata.",
|
||||
final fileLength = await trackPartialCacheFile.length();
|
||||
if (fileLength != contentRange.total) return;
|
||||
|
||||
await trackPartialCacheFile.rename(trackCacheFile.path);
|
||||
|
||||
if (track.qualityPreset!.getFileExtension() == "weba") return;
|
||||
|
||||
final imageBytes = await ServiceUtils.downloadImage(
|
||||
track.query.album.images.asUrlString(
|
||||
placeholder: ImagePlaceholder.albumArt,
|
||||
index: 1,
|
||||
),
|
||||
);
|
||||
return (response: res, bytes: bytes);
|
||||
}
|
||||
|
||||
final imageBytes = await ServiceUtils.downloadImage(
|
||||
(playlistTrack.album.images).asUrlString(
|
||||
placeholder: ImagePlaceholder.albumArt,
|
||||
index: 1,
|
||||
),
|
||||
);
|
||||
await MetadataGod.writeMetadata(
|
||||
file: trackCacheFile.path,
|
||||
metadata: track.query.toMetadata(
|
||||
imageBytes: imageBytes,
|
||||
fileLength: fileLength,
|
||||
),
|
||||
).catchError((e, stackTrace) {
|
||||
AppLogger.reportError(e, stackTrace);
|
||||
});
|
||||
},
|
||||
cancelOnError: true,
|
||||
);
|
||||
|
||||
await MetadataGod.writeMetadata(
|
||||
file: trackCacheFile.path,
|
||||
metadata: (playlistTrack as SpotubeFullTrackObject).toMetadata(
|
||||
imageBytes: imageBytes,
|
||||
fileLength: fileLength,
|
||||
),
|
||||
).catchError((e, stackTrace) {
|
||||
AppLogger.reportError(e, stackTrace);
|
||||
});
|
||||
}
|
||||
|
||||
return (bytes: bytes, response: res);
|
||||
res.data?.stream =
|
||||
resStream; // To avoid Stream has been already listened to exception
|
||||
return res;
|
||||
}
|
||||
|
||||
/// @head('/stream/<trackId>')
|
||||
@ -328,15 +308,23 @@ class ServerPlaybackRoutes {
|
||||
return Response.notFound("Track not found in the current queue");
|
||||
}
|
||||
|
||||
final (bytes: audioBytes, response: res) = await streamTrack(
|
||||
final res = await streamTrack(
|
||||
request,
|
||||
sourcedTrack,
|
||||
request.headers,
|
||||
);
|
||||
|
||||
if (res.data is ResponseBody) {
|
||||
return Response(
|
||||
res.statusCode!,
|
||||
body: (res.data as ResponseBody).stream,
|
||||
headers: res.headers.map,
|
||||
);
|
||||
}
|
||||
|
||||
return Response(
|
||||
res.statusCode!,
|
||||
body: audioBytes,
|
||||
body: res.data,
|
||||
headers: res.headers.map,
|
||||
);
|
||||
} catch (e, stack) {
|
||||
|
||||
79
pubspec.lock
79
pubspec.lock
@ -1530,65 +1530,58 @@ packages:
|
||||
media_kit:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: media_kit
|
||||
ref: HEAD
|
||||
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||
url: "https://github.com/media-kit/media-kit"
|
||||
source: git
|
||||
version: "1.2.0"
|
||||
name: media_kit
|
||||
sha256: "52a8e989babc431db0aa242f32a4a08e55f60662477ea09759a105d7cd6410da"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
media_kit_libs_android_audio:
|
||||
dependency: "direct overridden"
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "libs/android/media_kit_libs_android_audio"
|
||||
ref: HEAD
|
||||
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||
url: "https://github.com/media-kit/media-kit"
|
||||
source: git
|
||||
version: "1.3.7"
|
||||
name: media_kit_libs_android_audio
|
||||
sha256: "8f8f9759e537e12d66f08bc4d5279eb1bb21a0ccc519ff3442c68a9f3b6dd68b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.8"
|
||||
media_kit_libs_audio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "libs/universal/media_kit_libs_audio"
|
||||
ref: HEAD
|
||||
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||
url: "https://github.com/media-kit/media-kit"
|
||||
source: git
|
||||
version: "1.0.6"
|
||||
name: media_kit_libs_audio
|
||||
sha256: "81bf506c234e81e3ec536ba72f8f700a928543c14c345220210cae0411636316"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.7"
|
||||
media_kit_libs_ios_audio:
|
||||
dependency: "direct overridden"
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "libs/ios/media_kit_libs_ios_audio"
|
||||
ref: HEAD
|
||||
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||
url: "https://github.com/media-kit/media-kit"
|
||||
source: git
|
||||
name: media_kit_libs_ios_audio
|
||||
sha256: "78ccf04e27d6b4ba00a355578ccb39b772f00d48269a6ac3db076edf2d51934f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
media_kit_libs_linux:
|
||||
dependency: "direct overridden"
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "libs/linux/media_kit_libs_linux"
|
||||
ref: HEAD
|
||||
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||
url: "https://github.com/media-kit/media-kit"
|
||||
source: git
|
||||
name: media_kit_libs_linux
|
||||
sha256: "2b473399a49ec94452c4d4ae51cfc0f6585074398d74216092bf3d54aac37ecf"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
media_kit_libs_macos_audio:
|
||||
dependency: "direct overridden"
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "libs/macos/media_kit_libs_macos_audio"
|
||||
ref: HEAD
|
||||
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||
url: "https://github.com/media-kit/media-kit"
|
||||
source: git
|
||||
name: media_kit_libs_macos_audio
|
||||
sha256: "3be21844df98f286de32808592835073cdef2c1a10078bac135da790badca950"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
media_kit_libs_windows_audio:
|
||||
dependency: "direct overridden"
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "libs/windows/media_kit_libs_windows_audio"
|
||||
ref: HEAD
|
||||
resolved-ref: c9617f570b8c0ba02857e721997f78c053a856c1
|
||||
url: "https://github.com/media-kit/media-kit"
|
||||
source: git
|
||||
name: media_kit_libs_windows_audio
|
||||
sha256: c2fd558cc87b9d89a801141fcdffe02e338a3b21a41a18fbd63d5b221a1b8e53
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.9"
|
||||
menu_base:
|
||||
dependency: transitive
|
||||
|
||||
34
pubspec.yaml
34
pubspec.yaml
@ -61,7 +61,6 @@ dependencies:
|
||||
sdk: flutter
|
||||
flutter_native_splash: ^2.4.6
|
||||
flutter_riverpod: ^2.5.1
|
||||
flutter_secure_storage: ^9.2.4
|
||||
flutter_sharing_intent: ^1.1.0
|
||||
flutter_undraw: ^0.2.1
|
||||
form_builder_validators: ^11.1.1
|
||||
@ -82,14 +81,8 @@ dependencies:
|
||||
logger: ^2.0.2
|
||||
logging: ^1.3.0
|
||||
lrc: ^1.0.2
|
||||
media_kit:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: media_kit
|
||||
media_kit_libs_audio:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: libs/universal/media_kit_libs_audio
|
||||
media_kit: ^1.2.1
|
||||
media_kit_libs_audio: ^1.0.7
|
||||
metadata_god: ^1.1.0
|
||||
mime: ^2.0.0
|
||||
open_file: ^3.5.10
|
||||
@ -161,6 +154,7 @@ dependencies:
|
||||
flutter_markdown_plus: ^1.0.3
|
||||
pub_semver: ^2.2.0
|
||||
change_case: ^1.1.0
|
||||
flutter_secure_storage: ^9.2.4
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.4.13
|
||||
@ -191,32 +185,12 @@ dependency_overrides:
|
||||
flutter_svg: ^2.0.17
|
||||
intl: any
|
||||
collection: any
|
||||
flutter_secure_storage_platform_interface: 2.0.0
|
||||
flutter_secure_storage_linux:
|
||||
git:
|
||||
url: https://github.com/m-berto/flutter_secure_storage.git
|
||||
ref: patch-2
|
||||
path: flutter_secure_storage_linux
|
||||
flutter_secure_storage_platform_interface: 2.0.0
|
||||
media_kit_libs_android_audio:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: libs/android/media_kit_libs_android_audio
|
||||
media_kit_libs_ios_audio:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: libs/ios/media_kit_libs_ios_audio
|
||||
media_kit_libs_macos_audio:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: libs/macos/media_kit_libs_macos_audio
|
||||
media_kit_libs_windows_audio:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: libs/windows/media_kit_libs_windows_audio
|
||||
media_kit_libs_linux:
|
||||
git:
|
||||
url: https://github.com/media-kit/media-kit
|
||||
path: libs/linux/media_kit_libs_linux
|
||||
|
||||
flutter:
|
||||
generate: true
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 202 KiB |
Loading…
Reference in New Issue
Block a user