Skip to content

Commit

Permalink
fix: pre downloading not working properly, audio service circular dep…
Browse files Browse the repository at this point in the history
…s and sibling not loading for backend track
  • Loading branch information
KRTirtho committed Feb 2, 2023
1 parent 312f7fb commit 3ccb525
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 64 deletions.
9 changes: 9 additions & 0 deletions lib/components/player/sibling_tracks_sheet.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions lib/components/shared/image/universal_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
97 changes: 95 additions & 2 deletions lib/models/spotube_track.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down
115 changes: 53 additions & 62 deletions lib/provider/playlist_queue_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -81,6 +80,8 @@ class PlaylistQueue {

class PlaylistQueueNotifier extends PersistedStateNotifier<PlaylistQueue?> {
final Ref ref;
MobileAudioService? mobileService;
LinuxAudioService? linuxService;

static final provider =
StateNotifierProvider<PlaylistQueueNotifier, PlaylistQueue?>(
Expand All @@ -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();
});
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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,
);
}
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
),
);
});

0 comments on commit 3ccb525

Please sign in to comment.