mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-06 07:29:42 +00:00
Compare commits
7 Commits
fbb9afc8c3
...
0c9a545c33
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c9a545c33 | ||
|
|
973ca20c8e | ||
|
|
7d849b1430 | ||
|
|
e5150515f3 | ||
|
|
ff252d6b14 | ||
|
|
195cad8f39 | ||
|
|
19f525fa3c |
@ -5,6 +5,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
|
||||
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
|
||||
import 'package:spotube/collections/assets.gen.dart';
|
||||
import 'package:spotube/collections/routes.dart';
|
||||
import 'package:spotube/collections/spotube_icons.dart';
|
||||
import 'package:spotube/components/ui/button_tile.dart';
|
||||
import 'package:spotube/extensions/constrains.dart';
|
||||
@ -59,7 +60,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.delete,
|
||||
playlistId,
|
||||
);
|
||||
@ -73,7 +74,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.album,
|
||||
playlistId,
|
||||
);
|
||||
@ -97,7 +98,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.addToQueue,
|
||||
playlistId,
|
||||
);
|
||||
@ -110,7 +111,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.playNext,
|
||||
playlistId,
|
||||
);
|
||||
@ -124,7 +125,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.removeFromQueue,
|
||||
playlistId,
|
||||
);
|
||||
@ -139,7 +140,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.favorite,
|
||||
playlistId,
|
||||
);
|
||||
@ -162,7 +163,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.startRadio,
|
||||
playlistId,
|
||||
);
|
||||
@ -175,7 +176,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.addToPlaylist,
|
||||
playlistId,
|
||||
);
|
||||
@ -190,7 +191,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.removeFromPlaylist,
|
||||
playlistId,
|
||||
);
|
||||
@ -204,7 +205,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.download,
|
||||
playlistId,
|
||||
);
|
||||
@ -226,7 +227,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.blacklist,
|
||||
playlistId,
|
||||
);
|
||||
@ -250,7 +251,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.share,
|
||||
playlistId,
|
||||
);
|
||||
@ -264,7 +265,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.songlink,
|
||||
playlistId,
|
||||
);
|
||||
@ -282,7 +283,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
style: ButtonVariance.menu,
|
||||
onPressed: () async {
|
||||
await trackOptionActions.action(
|
||||
context,
|
||||
rootNavigatorKey.currentContext!,
|
||||
TrackOptionValue.details,
|
||||
playlistId,
|
||||
);
|
||||
|
||||
@ -137,16 +137,16 @@
|
||||
"pre_download_play_description": "Anzi che effettuare lo stream dell'audio, scarica invece i byte e li riproduce (raccomandato per gli utenti con banda più alta)",
|
||||
"skip_non_music": "Salta i segmenti non di musica (SponsorBlock)",
|
||||
"blacklist_description": "Tracce e artisti in blacklist",
|
||||
"wait_for_download_to_finish": "Prego attendere che lo scaricamento corrente finisca",
|
||||
"wait_for_download_to_finish": "Prego attendere che il download corrente finisca",
|
||||
"desktop": "Desktop",
|
||||
"close_behavior": "Comportamento Chiusura",
|
||||
"close": "Chiudi",
|
||||
"minimize_to_tray": "Minimizza in tray",
|
||||
"show_tray_icon": "Mostra icona in tray di sistema",
|
||||
"about": "A proposito di",
|
||||
"about": "Informazioni su",
|
||||
"u_love_spotube": "Sappiamo che ami Spotube",
|
||||
"check_for_updates": "Controlla aggiornamenti",
|
||||
"about_spotube": "A proposito di Spotube",
|
||||
"about_spotube": "Informazioni su Spotube",
|
||||
"blacklist": "Blacklist",
|
||||
"please_sponsor": "Per favore sponsorizza/dona",
|
||||
"spotube_description": "Spotube, un client spotify gratis per tutti, multipiattaforma e leggero",
|
||||
@ -187,7 +187,7 @@
|
||||
"generate_playlist": "Genera Playlist",
|
||||
"track_exists": "La traccia {track} esiste già",
|
||||
"replace_downloaded_tracks": "Sostituisci tutte le tracce scaricate",
|
||||
"skip_download_tracks": "Salta lo scaricamento di tutte le tracce scaricate",
|
||||
"skip_download_tracks": "Salta il download di tutte le tracce scaricate",
|
||||
"do_you_want_to_replace": "Vuoi sovrascrivere la traccia esistente??",
|
||||
"replace": "Sovrascrivi",
|
||||
"skip": "Salta",
|
||||
@ -256,7 +256,7 @@
|
||||
"querying_info": "Richiesta informazioni...",
|
||||
"piped_api_down": "Le Piped API non funzionano",
|
||||
"piped_down_error_instructions": "L'istanza di Piped {pipedInstance} è correntemente offline\n\nCambia istanza o cambia 'Tipo API' alle API ufficiali YouTube\n\nAssicurati di riavviare l'app dopo il cambio",
|
||||
"you_are_offline": "Sei correntemente offline",
|
||||
"you_are_offline": "Al momento sei offline",
|
||||
"connection_restored": "Connessione ad internet ripristinata",
|
||||
"use_system_title_bar": "Usa la barra del titolo di sistema",
|
||||
"crunching_results": "Elaborazione risultati...",
|
||||
@ -267,15 +267,15 @@
|
||||
"change_cover": "Cambia copertina",
|
||||
"add_cover": "Aggiungi copertina",
|
||||
"restore_defaults": "Ripristina default",
|
||||
"download_music_codec": "Codec musicale scaricamento",
|
||||
"streaming_music_codec": "Codec musicale streaming",
|
||||
"login_with_lastfm": "Accesso a Last.fm",
|
||||
"connect": "Connetti",
|
||||
"disconnect_lastfm": "Disconnetti Last.fm",
|
||||
"download_music_codec": "Codec download musica",
|
||||
"streaming_music_codec": "Codec streaming musica",
|
||||
"login_with_lastfm": "Accedi con Last.fm",
|
||||
"connect": "Connettiti",
|
||||
"disconnect_lastfm": "Disconnettiti da Last.fm",
|
||||
"disconnect": "Disconnetti",
|
||||
"username": "Nome utente",
|
||||
"password": "Password",
|
||||
"login": "Accesso",
|
||||
"login": "Accedi",
|
||||
"login_with_your_lastfm": "Accedi con il tuo account Last.fm",
|
||||
"scrobble_to_lastfm": "Invia a Last.fm",
|
||||
"audio_source": "Fonte audio",
|
||||
@ -299,7 +299,7 @@
|
||||
"song_link": "Link della Canzone",
|
||||
"skip_this_nonsense": "Salta questa sciocchezza",
|
||||
"freedom_of_music": "“Libertà della Musica”",
|
||||
"freedom_of_music_palm": "“Libertà della Musica nel palmo della tua mano”",
|
||||
"freedom_of_music_palm": "“Libertà della Musica nelle tue mani”",
|
||||
"get_started": "Cominciamo",
|
||||
"youtube_source_description": "Consigliato e funziona meglio.",
|
||||
"piped_source_description": "Ti senti libero? Come YouTube ma molto più gratuito.",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:flutter/material.dart' show Badge;
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
@ -83,7 +84,12 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget {
|
||||
runSpacing: 6,
|
||||
children: [
|
||||
for (final source in AudioSource.values)
|
||||
RadioCard(
|
||||
Badge(
|
||||
isLabelVisible: source == AudioSource.dabMusic,
|
||||
label: const Text("NEW"),
|
||||
backgroundColor: Colors.lime[300],
|
||||
textColor: Colors.black,
|
||||
child: RadioCard(
|
||||
value: source,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -93,6 +99,7 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -259,11 +259,14 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
||||
return addTracks(tracks);
|
||||
}
|
||||
|
||||
final addableTracks = _blacklist.filter(tracks).where(
|
||||
final addableTracks = _blacklist
|
||||
.filter(tracks)
|
||||
.where(
|
||||
(track) =>
|
||||
allowDuplicates ||
|
||||
!state.tracks.any((element) => _compareTracks(element, track)),
|
||||
);
|
||||
)
|
||||
.toList();
|
||||
|
||||
state = state.copyWith(
|
||||
tracks: [...addableTracks, ...state.tracks],
|
||||
@ -371,13 +374,12 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
||||
}
|
||||
|
||||
bool _compareTracks(SpotubeTrackObject a, SpotubeTrackObject b) {
|
||||
if ((a is SpotubeLocalTrackObject && b is! SpotubeLocalTrackObject) ||
|
||||
(a is! SpotubeLocalTrackObject && b is SpotubeLocalTrackObject)) {
|
||||
if (a.runtimeType != b.runtimeType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return a is SpotubeLocalTrackObject && b is SpotubeLocalTrackObject
|
||||
? (a).path == (b).path
|
||||
? a.path == b.path
|
||||
: a.id == b.id;
|
||||
}
|
||||
|
||||
|
||||
@ -214,7 +214,7 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
|
||||
/// Root directory where all metadata plugins are stored.
|
||||
Future<Directory> _getPluginRootDir() async => Directory(
|
||||
join(
|
||||
(await getApplicationCacheDirectory()).path,
|
||||
(await getApplicationSupportDirectory()).path,
|
||||
"metadata-plugins",
|
||||
),
|
||||
);
|
||||
|
||||
@ -166,7 +166,7 @@ class TrackOptionsActions {
|
||||
}
|
||||
break;
|
||||
case TrackOptionValue.playNext:
|
||||
playback.addTracksAtFirst([track]);
|
||||
await playback.addTracksAtFirst([track]);
|
||||
|
||||
if (context.mounted) {
|
||||
showToast(
|
||||
|
||||
@ -57,6 +57,7 @@ abstract class AudioPlayerInterface {
|
||||
title: "Spotube",
|
||||
logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error,
|
||||
bufferSize: 4 * 1024 * 1024, // 4MB buffer
|
||||
async: true,
|
||||
),
|
||||
) {
|
||||
_mkPlayer.stream.error.listen((event) {
|
||||
|
||||
@ -121,9 +121,23 @@ class CustomPlayer extends Player {
|
||||
NativePlayer get nativePlayer => platform as NativePlayer;
|
||||
|
||||
Future<void> insert(int index, Media media) async {
|
||||
final addedMediaCompleter = Completer<int>();
|
||||
final playlistStream = stream.playlist.listen(
|
||||
(event) {
|
||||
final mediaAddedIndex =
|
||||
event.medias.indexWhere((m) => m.uri == media.uri);
|
||||
if (mediaAddedIndex != -1 && !addedMediaCompleter.isCompleted) {
|
||||
addedMediaCompleter.complete(mediaAddedIndex);
|
||||
}
|
||||
},
|
||||
);
|
||||
try {
|
||||
await add(media);
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await move(state.playlist.medias.length - 1, index);
|
||||
final mediaAddedIndex = await addedMediaCompleter.future;
|
||||
await move(mediaAddedIndex, index);
|
||||
} finally {
|
||||
playlistStream.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setAudioNormalization(bool normalize) async {
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/models/playback/track_sources.dart';
|
||||
import 'package:spotube/provider/database/database.dart';
|
||||
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
|
||||
import 'package:spotube/services/logger/logger.dart';
|
||||
import 'package:spotube/services/sourced_track/enums.dart';
|
||||
@ -32,11 +36,68 @@ class DABMusicSourcedTrack extends SourcedTrack {
|
||||
required Ref ref,
|
||||
}) async {
|
||||
try {
|
||||
final database = ref.read(databaseProvider);
|
||||
final cachedSource = await (database.select(database.sourceMatchTable)
|
||||
..where((s) => s.trackId.equals(query.id))
|
||||
..limit(1)
|
||||
..orderBy([
|
||||
(s) => OrderingTerm(
|
||||
expression: s.createdAt,
|
||||
mode: OrderingMode.desc,
|
||||
),
|
||||
]))
|
||||
.get()
|
||||
.then((s) => s.firstOrNull);
|
||||
|
||||
if (cachedSource != null &&
|
||||
cachedSource.sourceType == SourceType.dabMusic) {
|
||||
final json = jsonDecode(cachedSource.sourceId);
|
||||
final info = TrackSourceInfo.fromJson(json["info"]);
|
||||
final source = (json["sources"] as List?)
|
||||
?.map((s) => TrackSource.fromJson(s))
|
||||
.toList();
|
||||
|
||||
final [updatedSource] = await fetchSources(
|
||||
info.id,
|
||||
ref.read(userPreferencesProvider).audioQuality,
|
||||
const AudioQuality(
|
||||
isHiRes: true,
|
||||
maximumBitDepth: 16,
|
||||
maximumSamplingRate: 44.1,
|
||||
),
|
||||
);
|
||||
|
||||
return DABMusicSourcedTrack(
|
||||
ref: ref,
|
||||
source: AudioSource.dabMusic,
|
||||
siblings: [],
|
||||
info: info,
|
||||
query: query,
|
||||
sources: [
|
||||
source!.first.copyWith(url: updatedSource.url),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final siblings = await fetchSiblings(ref: ref, query: query);
|
||||
|
||||
if (siblings.isEmpty) {
|
||||
throw TrackNotFoundError(query);
|
||||
}
|
||||
|
||||
await database.into(database.sourceMatchTable).insert(
|
||||
SourceMatchTableCompanion.insert(
|
||||
trackId: query.id,
|
||||
sourceId: jsonEncode({
|
||||
"info": siblings.first.info.toJson(),
|
||||
"sources": (siblings.first.source ?? [])
|
||||
.map((s) => s.toJson())
|
||||
.toList(),
|
||||
}),
|
||||
sourceType: const Value(SourceType.dabMusic),
|
||||
),
|
||||
);
|
||||
|
||||
return DABMusicSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: siblings.map((s) => s.info).skip(1).toList(),
|
||||
@ -207,6 +268,23 @@ class DABMusicSourcedTrack extends SourcedTrack {
|
||||
),
|
||||
);
|
||||
|
||||
final database = ref.read(databaseProvider);
|
||||
|
||||
await database.into(database.sourceMatchTable).insert(
|
||||
SourceMatchTableCompanion.insert(
|
||||
trackId: query.id,
|
||||
sourceId: jsonEncode({
|
||||
"info": newSourceInfo.toJson(),
|
||||
"sources": source.map((s) => s.toJson()).toList(),
|
||||
}),
|
||||
sourceType: const Value(SourceType.dabMusic),
|
||||
// Because we're sorting by createdAt in the query
|
||||
// we have to update it to indicate priority
|
||||
createdAt: Value(DateTime.now()),
|
||||
),
|
||||
mode: InsertMode.replace,
|
||||
);
|
||||
|
||||
return DABMusicSourcedTrack(
|
||||
ref: ref,
|
||||
siblings: newSiblings,
|
||||
|
||||
@ -1412,10 +1412,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: invidious
|
||||
sha256: "27ef3a001df875665de15535dbc9099f44d12a59480018fb1e17377d4af0308d"
|
||||
sha256: "0da8ebc4c4110057f03302bbd54514b10642154d7be569e7994172f2202dcfe8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
version: "0.1.2"
|
||||
io:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
||||
@ -81,7 +81,7 @@ dependencies:
|
||||
http: ^1.2.1
|
||||
image_picker: ^1.1.0
|
||||
intl: any
|
||||
invidious: ^0.1.1
|
||||
invidious: ^0.1.2
|
||||
jiosaavn: ^0.1.0
|
||||
json_annotation: ^4.8.1
|
||||
local_notifier: ^0.1.6
|
||||
|
||||
Loading…
Reference in New Issue
Block a user