mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
chore: make playback working
This commit is contained in:
parent
86e55f7a3d
commit
41cc79b5e6
File diff suppressed because one or more lines are too long
@ -9,7 +9,6 @@ import 'package:spotube/extensions/duration.dart';
|
||||
import 'package:spotube/models/metadata/metadata.dart';
|
||||
import 'package:spotube/models/playback/track_sources.dart';
|
||||
import 'package:spotube/provider/server/track_sources.dart';
|
||||
import 'package:spotube/services/sourced_track/sourced_track.dart';
|
||||
|
||||
class TrackDetailsDialog extends HookConsumerWidget {
|
||||
final SpotubeFullTrackObject track;
|
||||
@ -59,12 +58,13 @@ class TrackDetailsDialog extends HookConsumerWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
context.l10n.channel: Text(sourceInfo.artists),
|
||||
context.l10n.streamUrl: Hyperlink(
|
||||
(track as SourcedTrack).url,
|
||||
(track as SourcedTrack).url,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (sourcedTrack.asData?.value.url != null)
|
||||
context.l10n.streamUrl: Hyperlink(
|
||||
sourcedTrack.asData!.value.url,
|
||||
sourcedTrack.asData!.value.url,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
};
|
||||
|
||||
return AlertDialog(
|
||||
|
@ -214,14 +214,12 @@ class TrackOptions extends HookConsumerWidget {
|
||||
]);
|
||||
|
||||
final progressNotifier = useMemoized(() {
|
||||
if (track is! SpotubeFullTrackObject) {
|
||||
return throw Exception(
|
||||
"Invalid usage of `progressNotifierFuture`. Track must be a SpotubeFullTrackObject to get download progress",
|
||||
);
|
||||
if (track is SpotubeLocalTrackObject) {
|
||||
return null;
|
||||
}
|
||||
return downloadManager
|
||||
.getProgressNotifier(track as SpotubeFullTrackObject);
|
||||
});
|
||||
}, [downloadManager, track]);
|
||||
|
||||
final isLocalTrack = track is SpotubeLocalTrackObject;
|
||||
|
||||
@ -346,7 +344,7 @@ class TrackOptions extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
icon: icon ?? const Icon(SpotubeIcons.moreHorizontal),
|
||||
variance: ButtonVariance.outline,
|
||||
variance: ButtonVariance.ghost,
|
||||
headings: [
|
||||
Basic(
|
||||
leading: AspectRatio(
|
||||
|
@ -2930,7 +2930,9 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable
|
||||
@override
|
||||
late final GeneratedColumnWithTypeConverter<List<SpotubeTrackObject>, String>
|
||||
tracks = GeneratedColumn<String>('tracks', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true)
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const Constant("[]"))
|
||||
.withConverter<List<SpotubeTrackObject>>(
|
||||
$AudioPlayerStateTableTable.$convertertracks);
|
||||
static const VerificationMeta _currentIndexMeta =
|
||||
@ -2938,7 +2940,9 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable
|
||||
@override
|
||||
late final GeneratedColumn<int> currentIndex = GeneratedColumn<int>(
|
||||
'current_index', aliasedName, false,
|
||||
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const Constant(0));
|
||||
@override
|
||||
List<GeneratedColumn> get $columns =>
|
||||
[id, playing, loopMode, shuffled, collections, tracks, currentIndex];
|
||||
@ -2976,8 +2980,6 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable
|
||||
_currentIndexMeta,
|
||||
currentIndex.isAcceptableOrUnknown(
|
||||
data['current_index']!, _currentIndexMeta));
|
||||
} else if (isInserting) {
|
||||
context.missing(_currentIndexMeta);
|
||||
}
|
||||
return context;
|
||||
}
|
||||
@ -3189,14 +3191,12 @@ class AudioPlayerStateTableCompanion
|
||||
required PlaylistMode loopMode,
|
||||
required bool shuffled,
|
||||
required List<String> collections,
|
||||
required List<SpotubeTrackObject> tracks,
|
||||
required int currentIndex,
|
||||
this.tracks = const Value.absent(),
|
||||
this.currentIndex = const Value.absent(),
|
||||
}) : playing = Value(playing),
|
||||
loopMode = Value(loopMode),
|
||||
shuffled = Value(shuffled),
|
||||
collections = Value(collections),
|
||||
tracks = Value(tracks),
|
||||
currentIndex = Value(currentIndex);
|
||||
collections = Value(collections);
|
||||
static Insertable<AudioPlayerStateTableData> custom({
|
||||
Expression<int>? id,
|
||||
Expression<bool>? playing,
|
||||
@ -5751,8 +5751,8 @@ typedef $$AudioPlayerStateTableTableCreateCompanionBuilder
|
||||
required PlaylistMode loopMode,
|
||||
required bool shuffled,
|
||||
required List<String> collections,
|
||||
required List<SpotubeTrackObject> tracks,
|
||||
required int currentIndex,
|
||||
Value<List<SpotubeTrackObject>> tracks,
|
||||
Value<int> currentIndex,
|
||||
});
|
||||
typedef $$AudioPlayerStateTableTableUpdateCompanionBuilder
|
||||
= AudioPlayerStateTableCompanion Function({
|
||||
@ -5922,8 +5922,8 @@ class $$AudioPlayerStateTableTableTableManager extends RootTableManager<
|
||||
required PlaylistMode loopMode,
|
||||
required bool shuffled,
|
||||
required List<String> collections,
|
||||
required List<SpotubeTrackObject> tracks,
|
||||
required int currentIndex,
|
||||
Value<List<SpotubeTrackObject>> tracks = const Value.absent(),
|
||||
Value<int> currentIndex = const Value.absent(),
|
||||
}) =>
|
||||
AudioPlayerStateTableCompanion.insert(
|
||||
id: id,
|
||||
|
@ -1921,10 +1921,10 @@ class Shape14 extends i0.VersionedTable {
|
||||
|
||||
i1.GeneratedColumn<String> _column_57(String aliasedName) =>
|
||||
i1.GeneratedColumn<String>('tracks', aliasedName, false,
|
||||
type: i1.DriftSqlType.string);
|
||||
type: i1.DriftSqlType.string, defaultValue: const Constant("[]"));
|
||||
i1.GeneratedColumn<int> _column_58(String aliasedName) =>
|
||||
i1.GeneratedColumn<int>('current_index', aliasedName, false,
|
||||
type: i1.DriftSqlType.int);
|
||||
type: i1.DriftSqlType.int, defaultValue: const Constant(0));
|
||||
|
||||
class Shape15 extends i0.VersionedTable {
|
||||
Shape15({required super.source, required super.alias}) : super.aliased();
|
||||
|
@ -6,9 +6,10 @@ class AudioPlayerStateTable extends Table {
|
||||
TextColumn get loopMode => textEnum<PlaylistMode>()();
|
||||
BoolColumn get shuffled => boolean()();
|
||||
TextColumn get collections => text().map(const StringListConverter())();
|
||||
TextColumn get tracks =>
|
||||
text().map(const SpotubeTrackObjectListConverter())();
|
||||
IntColumn get currentIndex => integer()();
|
||||
TextColumn get tracks => text()
|
||||
.map(const SpotubeTrackObjectListConverter())
|
||||
.withDefault(const Constant("[]"))();
|
||||
IntColumn get currentIndex => integer().withDefault(const Constant(0))();
|
||||
}
|
||||
|
||||
class SpotubeTrackObjectListConverter
|
||||
|
@ -9,6 +9,8 @@ part 'track_sources.g.dart';
|
||||
|
||||
@freezed
|
||||
class TrackSourceQuery with _$TrackSourceQuery {
|
||||
TrackSourceQuery._();
|
||||
|
||||
factory TrackSourceQuery({
|
||||
required String id,
|
||||
required String title,
|
||||
@ -37,10 +39,23 @@ class TrackSourceQuery with _$TrackSourceQuery {
|
||||
/// Parses [SpotubeMedia]'s [uri] property to create a [TrackSourceQuery].
|
||||
factory TrackSourceQuery.parseUri(String url) {
|
||||
final uri = Uri.parse(url);
|
||||
return TrackSourceQuery.fromJson({
|
||||
"id": uri.pathSegments.last,
|
||||
...uri.queryParameters,
|
||||
});
|
||||
return TrackSourceQuery(
|
||||
id: uri.pathSegments.last,
|
||||
title: uri.queryParameters['title'] ?? '',
|
||||
artists: uri.queryParameters['artists']?.split(',') ?? [],
|
||||
album: uri.queryParameters['album'] ?? '',
|
||||
durationMs: int.tryParse(uri.queryParameters['durationMs'] ?? '0') ?? 0,
|
||||
isrc: uri.queryParameters['isrc'] ?? '',
|
||||
explicit: uri.queryParameters['explicit']?.toLowerCase() == 'true',
|
||||
);
|
||||
}
|
||||
|
||||
String queryString() {
|
||||
return toJson()
|
||||
.entries
|
||||
.map((e) =>
|
||||
"${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value is List<String> ? e.value.join(",") : e.value.toString())}")
|
||||
.join("&");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +184,7 @@ class __$$TrackSourceQueryImplCopyWithImpl<$Res>
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$TrackSourceQueryImpl implements _TrackSourceQuery {
|
||||
class _$TrackSourceQueryImpl extends _TrackSourceQuery {
|
||||
_$TrackSourceQueryImpl(
|
||||
{required this.id,
|
||||
required this.title,
|
||||
@ -193,7 +193,8 @@ class _$TrackSourceQueryImpl implements _TrackSourceQuery {
|
||||
required this.durationMs,
|
||||
required this.isrc,
|
||||
required this.explicit})
|
||||
: _artists = artists;
|
||||
: _artists = artists,
|
||||
super._();
|
||||
|
||||
factory _$TrackSourceQueryImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$TrackSourceQueryImplFromJson(json);
|
||||
@ -269,7 +270,7 @@ class _$TrackSourceQueryImpl implements _TrackSourceQuery {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _TrackSourceQuery implements TrackSourceQuery {
|
||||
abstract class _TrackSourceQuery extends TrackSourceQuery {
|
||||
factory _TrackSourceQuery(
|
||||
{required final String id,
|
||||
required final String title,
|
||||
@ -278,6 +279,7 @@ abstract class _TrackSourceQuery implements TrackSourceQuery {
|
||||
required final int durationMs,
|
||||
required final String isrc,
|
||||
required final bool explicit}) = _$TrackSourceQueryImpl;
|
||||
_TrackSourceQuery._() : super._();
|
||||
|
||||
factory _TrackSourceQuery.fromJson(Map<String, dynamic> json) =
|
||||
_$TrackSourceQueryImpl.fromJson;
|
||||
|
@ -62,10 +62,10 @@ class PlaylistCard extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
Future<List<SpotubeTrackObject>> fetchAllTracks() async {
|
||||
final initialTracks = await fetchInitialTracks();
|
||||
await fetchInitialTracks();
|
||||
|
||||
if (playlist.id == 'user-liked-tracks') {
|
||||
return initialTracks;
|
||||
return ref.read(metadataPluginSavedTracksProvider.notifier).fetchAll();
|
||||
}
|
||||
|
||||
return ref
|
||||
|
@ -2,7 +2,6 @@ import 'dart:math';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:media_kit/media_kit.dart' hide Track;
|
||||
import 'package:spotube/extensions/list.dart';
|
||||
import 'package:spotube/models/database/database.dart';
|
||||
import 'package:spotube/models/metadata/metadata.dart';
|
||||
@ -48,8 +47,8 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
||||
loopMode: audioPlayer.loopMode,
|
||||
shuffled: audioPlayer.isShuffled,
|
||||
collections: <String>[],
|
||||
tracks: <SpotubeTrackObject>[],
|
||||
currentIndex: 0,
|
||||
tracks: const Value(<SpotubeTrackObject>[]),
|
||||
currentIndex: const Value(0),
|
||||
id: const Value(0),
|
||||
),
|
||||
);
|
||||
@ -143,10 +142,12 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
||||
final queries = playlist.medias
|
||||
.map((media) => TrackSourceQuery.parseUri(media.uri))
|
||||
.toList();
|
||||
|
||||
final tracks = queries
|
||||
.map((query) => state.tracks.firstWhere(
|
||||
(element) => element.id == query.id,
|
||||
))
|
||||
.map(
|
||||
(query) => state.tracks
|
||||
.firstWhere((element) => element.id == query.id),
|
||||
)
|
||||
.toList();
|
||||
state = state.copyWith(
|
||||
tracks: tracks,
|
||||
@ -249,6 +250,10 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
||||
|
||||
if (_blacklist.contains(track)) return;
|
||||
if (state.tracks.any((element) => _compareTracks(element, track))) return;
|
||||
|
||||
state = state.copyWith(
|
||||
tracks: [...state.tracks, track],
|
||||
);
|
||||
await audioPlayer.addTrack(SpotubeMedia(track));
|
||||
}
|
||||
|
||||
@ -256,6 +261,9 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
||||
_assertAllowedTracks(tracks);
|
||||
|
||||
tracks = _blacklist.filter(tracks).toList();
|
||||
state = state.copyWith(
|
||||
tracks: [...state.tracks, ...tracks],
|
||||
);
|
||||
for (final track in tracks) {
|
||||
await audioPlayer.addTrack(SpotubeMedia(track));
|
||||
}
|
||||
@ -313,10 +321,15 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
|
||||
|
||||
if (medias.isEmpty) return;
|
||||
|
||||
await removeCollections(state.collections);
|
||||
state = state.copyWith(
|
||||
// These are filtered tracks as well
|
||||
tracks: medias.map((media) => media.track).toList(),
|
||||
currentIndex: initialIndex,
|
||||
collections: [],
|
||||
);
|
||||
|
||||
await audioPlayer.openPlaylist(
|
||||
medias.map((s) => s as Media).toList(),
|
||||
medias,
|
||||
initialIndex: initialIndex,
|
||||
autoPlay: autoPlay,
|
||||
);
|
||||
|
@ -19,8 +19,6 @@ part 'audio_player_impl.dart';
|
||||
class SpotubeMedia extends mk.Media {
|
||||
static int serverPort = 0;
|
||||
|
||||
final SpotubeTrackObject track;
|
||||
|
||||
static String get _host =>
|
||||
kIsWindows ? "localhost" : InternetAddress.anyIPv4.address;
|
||||
|
||||
@ -29,10 +27,11 @@ class SpotubeMedia extends mk.Media {
|
||||
|
||||
return params.entries
|
||||
.map((e) =>
|
||||
"${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}")
|
||||
"${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value is List<String> ? e.value.join(",") : e.value.toString())}")
|
||||
.join("&");
|
||||
}
|
||||
|
||||
final SpotubeTrackObject track;
|
||||
SpotubeMedia(
|
||||
this.track, {
|
||||
Map<String, dynamic>? extras,
|
||||
@ -47,32 +46,6 @@ class SpotubeMedia extends mk.Media {
|
||||
? track.path
|
||||
: "http://$_host:$serverPort/stream/${track.id}?${_queries(track as SpotubeFullTrackObject)}",
|
||||
);
|
||||
|
||||
@override
|
||||
String get uri {
|
||||
return switch (track) {
|
||||
/// [super.uri] must be used instead of [track.path] to prevent wrong
|
||||
/// path format exceptions in Windows causing [extras] to be null
|
||||
SpotubeLocalTrackObject() => super.uri,
|
||||
_ => "http://$_host:"
|
||||
"$serverPort/stream/${track.id}",
|
||||
};
|
||||
}
|
||||
|
||||
// @override
|
||||
// operator ==(Object other) {
|
||||
// if (other is! SpotubeMedia) return false;
|
||||
|
||||
// final isLocal = track is LocalTrack && other.track is LocalTrack;
|
||||
// return isLocal
|
||||
// ? (other.track as LocalTrack).path == (track as LocalTrack).path
|
||||
// : other.track.id == track.id;
|
||||
// }
|
||||
|
||||
// @override
|
||||
// int get hashCode => track is LocalTrack
|
||||
// ? (track as LocalTrack).path.hashCode
|
||||
// : track.id.hashCode;
|
||||
}
|
||||
|
||||
abstract class AudioPlayerInterface {
|
||||
|
@ -106,23 +106,27 @@ class MetadataPluginUserEndpoint {
|
||||
}
|
||||
|
||||
Future<List<bool>> isSavedTracks(List<String> ids) async {
|
||||
return await hetuMetadataUser.invoke(
|
||||
final values = await hetuMetadataUser.invoke(
|
||||
"isSavedTracks",
|
||||
positionalArgs: [ids],
|
||||
) as List<bool>;
|
||||
);
|
||||
return (values as List).cast<bool>();
|
||||
}
|
||||
|
||||
Future<List<bool>> isSavedAlbums(List<String> ids) async {
|
||||
return await hetuMetadataUser.invoke(
|
||||
final values = await hetuMetadataUser.invoke(
|
||||
"isSavedAlbums",
|
||||
positionalArgs: [ids],
|
||||
) as List<bool>;
|
||||
) as List;
|
||||
return values.cast<bool>();
|
||||
}
|
||||
|
||||
Future<List<bool>> isSavedArtists(List<String> ids) async {
|
||||
return await hetuMetadataUser.invoke(
|
||||
final values = await hetuMetadataUser.invoke(
|
||||
"isSavedArtists",
|
||||
positionalArgs: [ids],
|
||||
) as List<bool>;
|
||||
) as List;
|
||||
|
||||
return values.cast<bool>();
|
||||
}
|
||||
}
|
||||
|
@ -109,14 +109,15 @@ class YoutubeSourcedTrack extends SourcedTrack {
|
||||
return manifest.audioOnly.map((streamInfo) {
|
||||
return TrackSource(
|
||||
url: streamInfo.url.toString(),
|
||||
quality: streamInfo.qualityLabel == "AUDIO_QUALITY_HIGH"
|
||||
? SourceQualities.high
|
||||
: streamInfo.qualityLabel == "AUDIO_QUALITY_MEDIUM"
|
||||
? SourceQualities.medium
|
||||
: SourceQualities.low,
|
||||
codec: streamInfo.codec.mimeType == "audio/mp4"
|
||||
? SourceCodecs.m4a
|
||||
: SourceCodecs.weba,
|
||||
quality: switch (streamInfo.qualityLabel) {
|
||||
"medium" => SourceQualities.medium,
|
||||
"high" => SourceQualities.high,
|
||||
"low" => SourceQualities.low,
|
||||
_ => SourceQualities.high,
|
||||
},
|
||||
codec: streamInfo.codec.mimeType == "audio/webm"
|
||||
? SourceCodecs.weba
|
||||
: SourceCodecs.m4a,
|
||||
bitrate: streamInfo.bitrate.bitsPerSecond.toString(),
|
||||
);
|
||||
}).toList();
|
||||
|
@ -2813,10 +2813,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: youtube_explode_dart
|
||||
sha256: "3e1f1b5aa575670afc9dbc96cece23af78f9ec2044ce0d9f70d136fff6c53b53"
|
||||
sha256: "8db47e0f947598f6aa29d2862efb98b92af0c78990d4b23c224f3475c556b47b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0-dev.1"
|
||||
version: "2.4.2"
|
||||
yt_dlp_dart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -129,7 +129,7 @@ dependencies:
|
||||
wikipedia_api: ^0.1.0
|
||||
win32_registry: ^1.1.5
|
||||
window_manager: ^0.4.3
|
||||
youtube_explode_dart: ^2.4.0-dev.1
|
||||
youtube_explode_dart: ^2.4.2
|
||||
yt_dlp_dart:
|
||||
git:
|
||||
url: https://github.com/KRTirtho/yt_dlp_dart.git
|
||||
|
@ -2301,10 +2301,14 @@ class AudioPlayerStateTable extends Table
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
late final GeneratedColumn<String> tracks = GeneratedColumn<String>(
|
||||
'tracks', aliasedName, false,
|
||||
type: DriftSqlType.string, requiredDuringInsert: true);
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const Constant("[]"));
|
||||
late final GeneratedColumn<int> currentIndex = GeneratedColumn<int>(
|
||||
'current_index', aliasedName, false,
|
||||
type: DriftSqlType.int, requiredDuringInsert: true);
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: const Constant(0));
|
||||
@override
|
||||
List<GeneratedColumn> get $columns =>
|
||||
[id, playing, loopMode, shuffled, collections, tracks, currentIndex];
|
||||
@ -2499,14 +2503,12 @@ class AudioPlayerStateTableCompanion
|
||||
required String loopMode,
|
||||
required bool shuffled,
|
||||
required String collections,
|
||||
required String tracks,
|
||||
required int currentIndex,
|
||||
this.tracks = const Value.absent(),
|
||||
this.currentIndex = const Value.absent(),
|
||||
}) : playing = Value(playing),
|
||||
loopMode = Value(loopMode),
|
||||
shuffled = Value(shuffled),
|
||||
collections = Value(collections),
|
||||
tracks = Value(tracks),
|
||||
currentIndex = Value(currentIndex);
|
||||
collections = Value(collections);
|
||||
static Insertable<AudioPlayerStateTableData> custom({
|
||||
Expression<int>? id,
|
||||
Expression<bool>? playing,
|
||||
|
Loading…
Reference in New Issue
Block a user