fix(playback): alternative track sources switch not working

This commit is contained in:
Kingkor Roy Tirtho 2025-09-08 15:31:29 +06:00
parent 4a07945214
commit 58dc80aa09
5 changed files with 49 additions and 26 deletions

View File

@ -18,6 +18,7 @@ import 'package:spotube/hooks/utils/use_debounce.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/models/playback/track_sources.dart';
import 'package:spotube/provider/audio_player/audio_player.dart';
import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart';
import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/server/active_track_sources.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
@ -95,7 +96,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
final controller = useScrollController(); final controller = useScrollController();
final searchRequest = useMemoized(() async { final searchRequest = useMemoized(() async {
if (searchTerm.trim().isEmpty) { if (searchTerm.trim().isEmpty || activeTrackSource == null) {
return <TrackSourceInfo>[]; return <TrackSourceInfo>[];
} }
if (preferences.audioSource == AudioSource.jiosaavn) { if (preferences.audioSource == AudioSource.jiosaavn) {
@ -107,7 +108,7 @@ class SiblingTracksSheet extends HookConsumerWidget {
return siblingType.info; return siblingType.info;
})); }));
final activeSourceInfo = activeTrackSource?.info as TrackSourceInfo; final activeSourceInfo = activeTrackSource.info;
return results return results
..removeWhere((element) => element.id == activeSourceInfo.id) ..removeWhere((element) => element.id == activeSourceInfo.id)
@ -122,18 +123,18 @@ class SiblingTracksSheet extends HookConsumerWidget {
resultsYt resultsYt
.map(YoutubeVideoInfo.fromVideo) .map(YoutubeVideoInfo.fromVideo)
.mapIndexed((i, video) async { .mapIndexed((i, video) async {
if (!context.mounted) return null;
final siblingType = final siblingType =
await YoutubeSourcedTrack.toSiblingType(i, video, ref); await YoutubeSourcedTrack.toSiblingType(i, video, ref);
return siblingType.info; return siblingType.info;
}), })
.whereType<Future<TrackSourceInfo>>()
.toList(),
); );
final activeSourceInfo = activeTrackSource?.info as TrackSourceInfo; final activeSourceInfo = activeTrackSource.info;
return searchResults return searchResults
..removeWhere((element) => element.id == activeSourceInfo.id) ..removeWhere((element) => element.id == activeSourceInfo.id)
..insert( ..insert(0, activeSourceInfo);
0,
activeSourceInfo,
);
} }
}, [ }, [
searchTerm, searchTerm,
@ -165,8 +166,8 @@ class SiblingTracksSheet extends HookConsumerWidget {
}, [activeTrack, previousActiveTrack]); }, [activeTrack, previousActiveTrack]);
final itemBuilder = useCallback( final itemBuilder = useCallback(
(TrackSourceInfo sourceInfo) { (TrackSourceInfo sourceInfo, AudioSource source) {
final icon = sourceInfoToIconMap[sourceInfo.runtimeType]; final icon = sourceInfoToIconMap[source];
return ButtonTile( return ButtonTile(
style: ButtonVariance.ghost, style: ButtonVariance.ghost,
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
@ -197,16 +198,20 @@ class SiblingTracksSheet extends HookConsumerWidget {
enabled: !isFetchingActiveTrack, enabled: !isFetchingActiveTrack,
selected: !isFetchingActiveTrack && selected: !isFetchingActiveTrack &&
sourceInfo.id == activeTrackSource?.info.id, sourceInfo.id == activeTrackSource?.info.id,
onPressed: () { onPressed: () async {
if (!isFetchingActiveTrack && if (!isFetchingActiveTrack &&
sourceInfo.id != activeTrackSource?.info.id) { sourceInfo.id != activeTrackSource?.info.id) {
activeTrackNotifier?.swapWithSibling(sourceInfo); await activeTrackNotifier?.swapWithSibling(sourceInfo);
await ref.read(audioPlayerProvider.notifier).swapActiveSource();
if (context.mounted) {
if (MediaQuery.sizeOf(context).mdAndUp) { if (MediaQuery.sizeOf(context).mdAndUp) {
closeOverlay(context); closeOverlay(context);
} else { } else {
closeDrawer(context); closeDrawer(context);
} }
} }
}
}, },
); );
}, },
@ -301,8 +306,8 @@ class SiblingTracksSheet extends HookConsumerWidget {
controller: controller, controller: controller,
itemCount: siblings.length, itemCount: siblings.length,
separatorBuilder: (context, index) => const Gap(8), separatorBuilder: (context, index) => const Gap(8),
itemBuilder: (context, index) => itemBuilder: (context, index) => itemBuilder(
itemBuilder(siblings[index]), siblings[index], activeTrackSource!.source),
), ),
true => FutureBuilder( true => FutureBuilder(
future: searchRequest, future: searchRequest,
@ -321,8 +326,10 @@ class SiblingTracksSheet extends HookConsumerWidget {
controller: controller, controller: controller,
itemCount: snapshot.data!.length, itemCount: snapshot.data!.length,
separatorBuilder: (context, index) => const Gap(8), separatorBuilder: (context, index) => const Gap(8),
itemBuilder: (context, index) => itemBuilder: (context, index) => itemBuilder(
itemBuilder(snapshot.data![index]), snapshot.data![index],
preferences.audioSource,
),
); );
}, },
), ),

View File

@ -421,6 +421,20 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
); );
} }
Future<void> swapActiveSource() async {
if (state.tracks.isEmpty || state.activeTrack is! SpotubeFullTrackObject) {
return;
}
final currentIndex = state.currentIndex;
final currentTrack = state.activeTrack as SpotubeFullTrackObject;
final swappedMedia = SpotubeMedia(currentTrack);
await audioPlayer.addTrackAt(swappedMedia, currentIndex + 1);
await audioPlayer.skipToNext();
await audioPlayer.removeTrack(currentIndex);
}
Future<void> jumpToTrack(SpotubeTrackObject track) async { Future<void> jumpToTrack(SpotubeTrackObject track) async {
final index = final index =
state.tracks.toList().indexWhere((element) => element.id == track.id); state.tracks.toList().indexWhere((element) => element.id == track.id);

View File

@ -216,7 +216,7 @@ class JioSaavnSourcedTrack extends SourcedTrack {
ref: ref, ref: ref,
siblings: newSiblings, siblings: newSiblings,
sources: source!, sources: source!,
info: info, info: newSourceInfo,
query: query, query: query,
source: AudioSource.jiosaavn, source: AudioSource.jiosaavn,
); );

View File

@ -269,7 +269,7 @@ class PipedSourcedTrack extends SourcedTrack {
ref: ref, ref: ref,
siblings: newSiblings, siblings: newSiblings,
sources: toSources(manifest), sources: toSources(manifest),
info: info, info: newSourceInfo,
query: query, query: query,
source: source, source: source,
); );

View File

@ -120,6 +120,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
dynamic ref, dynamic ref,
) async { ) async {
assert(ref is WidgetRef || ref is Ref, "Invalid ref type"); assert(ref is WidgetRef || ref is Ref, "Invalid ref type");
List<TrackSource>? sourceMap; List<TrackSource>? sourceMap;
if (index == 0) { if (index == 0) {
final manifest = final manifest =
@ -338,6 +339,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
final newSourceInfo = isStepSibling final newSourceInfo = isStepSibling
? sibling ? sibling
: siblings.firstWhere((s) => s.id == sibling.id); : siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList() final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, info); ..insert(0, info);
@ -364,7 +366,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
source: source, source: source,
siblings: newSiblings, siblings: newSiblings,
sources: toTrackSources(manifest), sources: toTrackSources(manifest),
info: info, info: newSourceInfo,
query: query, query: query,
); );
} }