mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00
fix: pre downloading not working properly, audio service circular deps and sibling not loading for backend track
This commit is contained in:
parent
312f7fbe77
commit
3ccb525260
@ -1,6 +1,7 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:platform_ui/platform_ui.dart';
|
||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||
@ -32,6 +33,14 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
||||
topRight: Radius.circular(10),
|
||||
);
|
||||
|
||||
useEffect(() {
|
||||
if (playlist?.activeTrack is SpotubeTrack &&
|
||||
(playlist?.activeTrack as SpotubeTrack).siblings.isEmpty) {
|
||||
playlistNotifier.populateSibling();
|
||||
}
|
||||
return null;
|
||||
}, [playlist?.activeTrack]);
|
||||
|
||||
return BackdropFilter(
|
||||
filter: ImageFilter.blur(
|
||||
sigmaX: 12.0,
|
||||
|
@ -35,6 +35,8 @@ class UniversalImage extends HookWidget {
|
||||
cacheKey: path,
|
||||
scale: scale,
|
||||
);
|
||||
} else if (path.startsWith("assets/")) {
|
||||
return AssetImage(path);
|
||||
} else if (Uri.tryParse(path) != null) {
|
||||
return FileImage(File(path), scale: scale);
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/extensions/video.dart';
|
||||
import 'package:spotube/extensions/album_simple.dart';
|
||||
@ -97,8 +101,8 @@ class SpotubeTrack extends Track {
|
||||
VideoSearchList videos = await PrimitiveUtils.raceMultiple(
|
||||
() => youtube.search.search("${artists.join(", ")} - $title"),
|
||||
);
|
||||
siblings = videos.take(10).toList();
|
||||
ytVideo = videos.where((video) => !video.isLive).first;
|
||||
siblings = videos.where((video) => !video.isLive).take(10).toList();
|
||||
ytVideo = siblings.first;
|
||||
}
|
||||
|
||||
StreamManifest trackManifest = await PrimitiveUtils.raceMultiple(
|
||||
@ -132,6 +136,36 @@ class SpotubeTrack extends Track {
|
||||
);
|
||||
}
|
||||
|
||||
if (preferences.androidBytesPlay) {
|
||||
await DefaultCacheManager().getFileFromCache(track.id!).then(
|
||||
(file) async {
|
||||
if (file != null) return file.file;
|
||||
final List<int> bytesStore = [];
|
||||
final bytesFuture = Completer<Uint8List>();
|
||||
|
||||
youtube.videos.streams.get(chosenStreamInfo).listen(
|
||||
(data) {
|
||||
bytesStore.addAll(data);
|
||||
},
|
||||
onDone: () {
|
||||
bytesFuture.complete(Uint8List.fromList(bytesStore));
|
||||
},
|
||||
onError: (e) {
|
||||
bytesFuture.completeError(e);
|
||||
},
|
||||
);
|
||||
|
||||
final cached = await DefaultCacheManager().putFile(
|
||||
track.id!,
|
||||
await bytesFuture.future,
|
||||
fileExtension: chosenStreamInfo.codec.mimeType.split("/").last,
|
||||
);
|
||||
|
||||
return cached;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return SpotubeTrack.fromTrack(
|
||||
track: track,
|
||||
ytTrack: ytVideo,
|
||||
@ -197,6 +231,37 @@ class SpotubeTrack extends Track {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (preferences.androidBytesPlay) {
|
||||
await DefaultCacheManager().getFileFromCache(id!).then(
|
||||
(file) async {
|
||||
if (file != null) return file.file;
|
||||
final List<int> bytesStore = [];
|
||||
final bytesFuture = Completer<Uint8List>();
|
||||
|
||||
youtube.videos.streams.get(chosenStreamInfo).listen(
|
||||
(data) {
|
||||
bytesStore.addAll(data);
|
||||
},
|
||||
onDone: () {
|
||||
bytesFuture.complete(Uint8List.fromList(bytesStore));
|
||||
},
|
||||
onError: (e) {
|
||||
bytesFuture.completeError(e);
|
||||
},
|
||||
);
|
||||
|
||||
final cached = await DefaultCacheManager().putFile(
|
||||
id!,
|
||||
await bytesFuture.future,
|
||||
fileExtension: chosenStreamInfo.codec.mimeType.split("/").last,
|
||||
);
|
||||
|
||||
return cached;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return SpotubeTrack.fromTrack(
|
||||
track: this,
|
||||
ytTrack: video,
|
||||
@ -224,6 +289,34 @@ class SpotubeTrack extends Track {
|
||||
);
|
||||
}
|
||||
|
||||
Future<SpotubeTrack> populatedCopy() async {
|
||||
if (this.siblings.isNotEmpty) return this;
|
||||
final artists = (this.artists ?? [])
|
||||
.map((ar) => ar.name)
|
||||
.toList()
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
|
||||
final title = ServiceUtils.getTitle(
|
||||
name!,
|
||||
artists: artists,
|
||||
onlyCleanArtist: true,
|
||||
).trim();
|
||||
VideoSearchList videos = await PrimitiveUtils.raceMultiple(
|
||||
() => youtube.search.search("${artists.join(", ")} - $title"),
|
||||
);
|
||||
|
||||
final siblings = videos.where((video) => !video.isLive).take(10).toList();
|
||||
|
||||
return SpotubeTrack.fromTrack(
|
||||
track: this,
|
||||
ytTrack: ytTrack,
|
||||
ytUri: ytUri,
|
||||
skipSegments: skipSegments,
|
||||
siblings: siblings,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"album": album?.toJson(),
|
||||
|
@ -2,7 +2,6 @@ import 'package:audio_service/audio_service.dart';
|
||||
import 'package:audioplayers/audioplayers.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/models/local_track.dart';
|
||||
import 'package:spotube/models/spotube_track.dart';
|
||||
@ -81,6 +80,8 @@ class PlaylistQueue {
|
||||
|
||||
class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
final Ref ref;
|
||||
MobileAudioService? mobileService;
|
||||
LinuxAudioService? linuxService;
|
||||
|
||||
static final provider =
|
||||
StateNotifierProvider<PlaylistQueueNotifier, PlaylistQueue?>(
|
||||
@ -94,6 +95,19 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
}
|
||||
|
||||
void configure() async {
|
||||
if (kIsMobile || kIsMacOS) {
|
||||
mobileService = await AudioService.init(
|
||||
builder: () => MobileAudioService(ref),
|
||||
config: const AudioServiceConfig(
|
||||
androidNotificationChannelId: 'com.krtirtho.Spotube',
|
||||
androidNotificationChannelName: 'Spotube',
|
||||
androidNotificationOngoing: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (kIsLinux) {
|
||||
linuxService = LinuxAudioService(ref);
|
||||
}
|
||||
addListener((state) {
|
||||
linuxService?.player.updateProperties();
|
||||
});
|
||||
@ -137,9 +151,6 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
UserPreferences get preferences => ref.read(userPreferencesProvider);
|
||||
BlackListNotifier get blacklist =>
|
||||
ref.read(BlackListNotifier.provider.notifier);
|
||||
LinuxAudioService? get linuxService => ref.read(linuxAudioServiceProvider);
|
||||
Future<MobileAudioService?> get mobileService =>
|
||||
ref.read(mobileAudioServiceProvider);
|
||||
|
||||
bool get isLoaded => state != null;
|
||||
bool get isShuffled => _tempTracks.isNotEmpty;
|
||||
@ -205,14 +216,37 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
await play();
|
||||
}
|
||||
|
||||
Future<void> populateSibling() async {
|
||||
if (!isLoaded || state!.isLoading) return;
|
||||
final tracks = state!.tracks.toList();
|
||||
final track = await (state!.activeTrack as SpotubeTrack).populatedCopy();
|
||||
tracks[state!.active] = track;
|
||||
state = state!.copyWith(tracks: Set.from(tracks));
|
||||
}
|
||||
|
||||
Future<void> play() async {
|
||||
if (!isLoaded) return;
|
||||
pause();
|
||||
(await mobileService)?.session?.setActive(true);
|
||||
await pause();
|
||||
await mobileService?.session?.setActive(true);
|
||||
final mediaItem = MediaItem(
|
||||
id: state!.activeTrack.id!,
|
||||
title: state!.activeTrack.name!,
|
||||
album: state!.activeTrack.album?.name,
|
||||
artist: TypeConversionUtils.artists_X_String(
|
||||
state!.activeTrack.artists ?? <ArtistSimple>[]),
|
||||
artUri: Uri.parse(
|
||||
TypeConversionUtils.image_X_UrlString(
|
||||
state!.activeTrack.album?.images,
|
||||
placeholder: ImagePlaceholder.online,
|
||||
),
|
||||
),
|
||||
duration: state!.activeTrack.duration,
|
||||
);
|
||||
mobileService?.addItem(mediaItem);
|
||||
if (state!.activeTrack is LocalTrack) {
|
||||
await audioPlayer.play(
|
||||
DeviceFileSource((state!.activeTrack as LocalTrack).path),
|
||||
mode: PlayerMode.lowLatency,
|
||||
mode: PlayerMode.mediaPlayer,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@ -224,47 +258,22 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
);
|
||||
state = state!.copyWith(tracks: Set.from(tracks));
|
||||
}
|
||||
(await mobileService)?.addItem(
|
||||
MediaItem(
|
||||
id: state!.activeTrack.id!,
|
||||
title: state!.activeTrack.name!,
|
||||
album: state!.activeTrack.album?.name,
|
||||
artist: TypeConversionUtils.artists_X_String(
|
||||
state!.activeTrack.artists ?? <ArtistSimple>[]),
|
||||
artUri: Uri.parse(
|
||||
TypeConversionUtils.image_X_UrlString(
|
||||
state!.activeTrack.album?.images,
|
||||
placeholder: ImagePlaceholder.online,
|
||||
),
|
||||
),
|
||||
duration: (state!.activeTrack as SpotubeTrack).ytTrack.duration,
|
||||
),
|
||||
);
|
||||
if (preferences.androidBytesPlay) {
|
||||
final cached = await DefaultCacheManager()
|
||||
.getFileFromCache(state!.activeTrack.id!)
|
||||
.then(
|
||||
(file) async {
|
||||
if (file != null) return file.file;
|
||||
final downloaded = await DefaultCacheManager()
|
||||
.downloadFile((state!.activeTrack as SpotubeTrack).ytUri);
|
||||
final cached = await DefaultCacheManager().putFile(
|
||||
state!.activeTrack.id!,
|
||||
await downloaded.file.readAsBytes(),
|
||||
fileExtension: extension(downloaded.file.path).replaceAll('.', ''),
|
||||
);
|
||||
await DefaultCacheManager().removeFile(downloaded.originalUrl);
|
||||
return cached;
|
||||
},
|
||||
);
|
||||
|
||||
mobileService?.addItem(mediaItem.copyWith(
|
||||
duration: (state!.activeTrack as SpotubeTrack).ytTrack.duration,
|
||||
));
|
||||
|
||||
final cached =
|
||||
await DefaultCacheManager().getFileFromCache(state!.activeTrack.id!);
|
||||
if (preferences.androidBytesPlay && cached != null) {
|
||||
await audioPlayer.play(
|
||||
DeviceFileSource(cached.path),
|
||||
mode: PlayerMode.lowLatency,
|
||||
DeviceFileSource(cached.file.path),
|
||||
mode: PlayerMode.mediaPlayer,
|
||||
);
|
||||
} else {
|
||||
await audioPlayer.play(
|
||||
UrlSource((state!.activeTrack as SpotubeTrack).ytUri),
|
||||
mode: PlayerMode.lowLatency,
|
||||
mode: PlayerMode.mediaPlayer,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -299,7 +308,7 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
(await mobileService)?.session?.setActive(false);
|
||||
(mobileService)?.session?.setActive(false);
|
||||
state = null;
|
||||
_tempTracks = {};
|
||||
return audioPlayer.stop();
|
||||
@ -407,21 +416,3 @@ class VolumeProvider extends PersistedStateNotifier<double> {
|
||||
return {'volume': state};
|
||||
}
|
||||
}
|
||||
|
||||
final linuxAudioServiceProvider = Provider<LinuxAudioService?>((ref) {
|
||||
if (!kIsLinux) return null;
|
||||
return LinuxAudioService(ref);
|
||||
});
|
||||
|
||||
final mobileAudioServiceProvider =
|
||||
Provider<Future<MobileAudioService?>>((ref) async {
|
||||
if (!kIsMobile) return null;
|
||||
return AudioService.init(
|
||||
builder: () => MobileAudioService(ref),
|
||||
config: const AudioServiceConfig(
|
||||
androidNotificationChannelId: 'com.krtirtho.Spotube',
|
||||
androidNotificationChannelName: 'Spotube',
|
||||
androidNotificationOngoing: true,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user