mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05: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 'dart:ui';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:platform_ui/platform_ui.dart';
|
import 'package:platform_ui/platform_ui.dart';
|
||||||
import 'package:spotube/components/shared/image/universal_image.dart';
|
import 'package:spotube/components/shared/image/universal_image.dart';
|
||||||
@ -32,6 +33,14 @@ class SiblingTracksSheet extends HookConsumerWidget {
|
|||||||
topRight: Radius.circular(10),
|
topRight: Radius.circular(10),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (playlist?.activeTrack is SpotubeTrack &&
|
||||||
|
(playlist?.activeTrack as SpotubeTrack).siblings.isEmpty) {
|
||||||
|
playlistNotifier.populateSibling();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [playlist?.activeTrack]);
|
||||||
|
|
||||||
return BackdropFilter(
|
return BackdropFilter(
|
||||||
filter: ImageFilter.blur(
|
filter: ImageFilter.blur(
|
||||||
sigmaX: 12.0,
|
sigmaX: 12.0,
|
||||||
|
@ -35,6 +35,8 @@ class UniversalImage extends HookWidget {
|
|||||||
cacheKey: path,
|
cacheKey: path,
|
||||||
scale: scale,
|
scale: scale,
|
||||||
);
|
);
|
||||||
|
} else if (path.startsWith("assets/")) {
|
||||||
|
return AssetImage(path);
|
||||||
} else if (Uri.tryParse(path) != null) {
|
} else if (Uri.tryParse(path) != null) {
|
||||||
return FileImage(File(path), scale: scale);
|
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:spotify/spotify.dart';
|
||||||
import 'package:spotube/extensions/video.dart';
|
import 'package:spotube/extensions/video.dart';
|
||||||
import 'package:spotube/extensions/album_simple.dart';
|
import 'package:spotube/extensions/album_simple.dart';
|
||||||
@ -97,8 +101,8 @@ class SpotubeTrack extends Track {
|
|||||||
VideoSearchList videos = await PrimitiveUtils.raceMultiple(
|
VideoSearchList videos = await PrimitiveUtils.raceMultiple(
|
||||||
() => youtube.search.search("${artists.join(", ")} - $title"),
|
() => youtube.search.search("${artists.join(", ")} - $title"),
|
||||||
);
|
);
|
||||||
siblings = videos.take(10).toList();
|
siblings = videos.where((video) => !video.isLive).take(10).toList();
|
||||||
ytVideo = videos.where((video) => !video.isLive).first;
|
ytVideo = siblings.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamManifest trackManifest = await PrimitiveUtils.raceMultiple(
|
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(
|
return SpotubeTrack.fromTrack(
|
||||||
track: track,
|
track: track,
|
||||||
ytTrack: ytVideo,
|
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(
|
return SpotubeTrack.fromTrack(
|
||||||
track: this,
|
track: this,
|
||||||
ytTrack: video,
|
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() {
|
Map<String, dynamic> toJson() {
|
||||||
return {
|
return {
|
||||||
"album": album?.toJson(),
|
"album": album?.toJson(),
|
||||||
|
@ -2,7 +2,6 @@ import 'package:audio_service/audio_service.dart';
|
|||||||
import 'package:audioplayers/audioplayers.dart';
|
import 'package:audioplayers/audioplayers.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:path/path.dart';
|
|
||||||
import 'package:spotify/spotify.dart';
|
import 'package:spotify/spotify.dart';
|
||||||
import 'package:spotube/models/local_track.dart';
|
import 'package:spotube/models/local_track.dart';
|
||||||
import 'package:spotube/models/spotube_track.dart';
|
import 'package:spotube/models/spotube_track.dart';
|
||||||
@ -81,6 +80,8 @@ class PlaylistQueue {
|
|||||||
|
|
||||||
class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
|
MobileAudioService? mobileService;
|
||||||
|
LinuxAudioService? linuxService;
|
||||||
|
|
||||||
static final provider =
|
static final provider =
|
||||||
StateNotifierProvider<PlaylistQueueNotifier, PlaylistQueue?>(
|
StateNotifierProvider<PlaylistQueueNotifier, PlaylistQueue?>(
|
||||||
@ -94,6 +95,19 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void configure() async {
|
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) {
|
addListener((state) {
|
||||||
linuxService?.player.updateProperties();
|
linuxService?.player.updateProperties();
|
||||||
});
|
});
|
||||||
@ -137,9 +151,6 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
UserPreferences get preferences => ref.read(userPreferencesProvider);
|
UserPreferences get preferences => ref.read(userPreferencesProvider);
|
||||||
BlackListNotifier get blacklist =>
|
BlackListNotifier get blacklist =>
|
||||||
ref.read(BlackListNotifier.provider.notifier);
|
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 isLoaded => state != null;
|
||||||
bool get isShuffled => _tempTracks.isNotEmpty;
|
bool get isShuffled => _tempTracks.isNotEmpty;
|
||||||
@ -205,14 +216,37 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
await play();
|
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 {
|
Future<void> play() async {
|
||||||
if (!isLoaded) return;
|
if (!isLoaded) return;
|
||||||
pause();
|
await pause();
|
||||||
(await mobileService)?.session?.setActive(true);
|
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) {
|
if (state!.activeTrack is LocalTrack) {
|
||||||
await audioPlayer.play(
|
await audioPlayer.play(
|
||||||
DeviceFileSource((state!.activeTrack as LocalTrack).path),
|
DeviceFileSource((state!.activeTrack as LocalTrack).path),
|
||||||
mode: PlayerMode.lowLatency,
|
mode: PlayerMode.mediaPlayer,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -224,47 +258,22 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
|
|||||||
);
|
);
|
||||||
state = state!.copyWith(tracks: Set.from(tracks));
|
state = state!.copyWith(tracks: Set.from(tracks));
|
||||||
}
|
}
|
||||||
(await mobileService)?.addItem(
|
|
||||||
MediaItem(
|
mobileService?.addItem(mediaItem.copyWith(
|
||||||
id: state!.activeTrack.id!,
|
duration: (state!.activeTrack as SpotubeTrack).ytTrack.duration,
|
||||||
title: state!.activeTrack.name!,
|
));
|
||||||
album: state!.activeTrack.album?.name,
|
|
||||||
artist: TypeConversionUtils.artists_X_String(
|
final cached =
|
||||||
state!.activeTrack.artists ?? <ArtistSimple>[]),
|
await DefaultCacheManager().getFileFromCache(state!.activeTrack.id!);
|
||||||
artUri: Uri.parse(
|
if (preferences.androidBytesPlay && cached != null) {
|
||||||
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;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await audioPlayer.play(
|
await audioPlayer.play(
|
||||||
DeviceFileSource(cached.path),
|
DeviceFileSource(cached.file.path),
|
||||||
mode: PlayerMode.lowLatency,
|
mode: PlayerMode.mediaPlayer,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await audioPlayer.play(
|
await audioPlayer.play(
|
||||||
UrlSource((state!.activeTrack as SpotubeTrack).ytUri),
|
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 {
|
Future<void> stop() async {
|
||||||
(await mobileService)?.session?.setActive(false);
|
(mobileService)?.session?.setActive(false);
|
||||||
state = null;
|
state = null;
|
||||||
_tempTracks = {};
|
_tempTracks = {};
|
||||||
return audioPlayer.stop();
|
return audioPlayer.stop();
|
||||||
@ -407,21 +416,3 @@ class VolumeProvider extends PersistedStateNotifier<double> {
|
|||||||
return {'volume': state};
|
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