fix: pre downloading not working properly, audio service circular deps and sibling not loading for backend track

This commit is contained in:
Kingkor Roy Tirtho 2023-02-02 19:53:35 +06:00
parent 312f7fbe77
commit 3ccb525260
4 changed files with 159 additions and 64 deletions

View File

@ -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,

View File

@ -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);
}

View File

@ -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(),

View File

@ -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,27 +216,19 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
await play();
}
Future<void> play() async {
if (!isLoaded) return;
pause();
(await mobileService)?.session?.setActive(true);
if (state!.activeTrack is LocalTrack) {
await audioPlayer.play(
DeviceFileSource((state!.activeTrack as LocalTrack).path),
mode: PlayerMode.lowLatency,
);
return;
}
if (state!.activeTrack is! SpotubeTrack) {
Future<void> populateSibling() async {
if (!isLoaded || state!.isLoading) return;
final tracks = state!.tracks.toList();
tracks[state!.active] = await SpotubeTrack.fromFetchTrack(
state!.activeTrack,
preferences,
);
final track = await (state!.activeTrack as SpotubeTrack).populatedCopy();
tracks[state!.active] = track;
state = state!.copyWith(tracks: Set.from(tracks));
}
(await mobileService)?.addItem(
MediaItem(
Future<void> play() async {
if (!isLoaded) return;
await pause();
await mobileService?.session?.setActive(true);
final mediaItem = MediaItem(
id: state!.activeTrack.id!,
title: state!.activeTrack.name!,
album: state!.activeTrack.album?.name,
@ -237,34 +240,40 @@ class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
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;
},
duration: state!.activeTrack.duration,
);
mobileService?.addItem(mediaItem);
if (state!.activeTrack is LocalTrack) {
await audioPlayer.play(
DeviceFileSource(cached.path),
mode: PlayerMode.lowLatency,
DeviceFileSource((state!.activeTrack as LocalTrack).path),
mode: PlayerMode.mediaPlayer,
);
return;
}
if (state!.activeTrack is! SpotubeTrack) {
final tracks = state!.tracks.toList();
tracks[state!.active] = await SpotubeTrack.fromFetchTrack(
state!.activeTrack,
preferences,
);
state = state!.copyWith(tracks: Set.from(tracks));
}
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.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,
),
);
});