mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
fix: use id based source getters instead of index
This commit is contained in:
parent
75b46e1a17
commit
a0744630ba
@ -64,7 +64,10 @@ mixin NextFetcher on StateNotifier<ProxyPlaylist> {
|
||||
|
||||
/// Returns [Track.id] from [isUnPlayable] source that is not playable
|
||||
String getIdFromUnPlayable(String source) {
|
||||
return source.replaceFirst('https://youtube.com/unplayable.m4a?id=', '');
|
||||
return source
|
||||
.split('&')
|
||||
.first
|
||||
.replaceFirst('https://youtube.com/unplayable.m4a?id=', '');
|
||||
}
|
||||
|
||||
/// Returns appropriate Media source for [Track]
|
||||
@ -78,19 +81,25 @@ mixin NextFetcher on StateNotifier<ProxyPlaylist> {
|
||||
} else if (track is LocalTrack) {
|
||||
return track.path;
|
||||
} else {
|
||||
return "https://youtube.com/unplayable.m4a?id=${track.id}";
|
||||
return "https://youtube.com/unplayable.m4a?id=${track.id}&title=${track.name?.replaceAll(
|
||||
RegExp(r'\s+', caseSensitive: false),
|
||||
'-',
|
||||
)}";
|
||||
}
|
||||
}
|
||||
|
||||
List<Track> mapSourcesToTracks(List<String> sources) {
|
||||
final tracks = state.tracks;
|
||||
|
||||
return sources.map((source) {
|
||||
final track = tracks.firstWhereOrNull(
|
||||
(track) => makeAppropriateSource(track) == source,
|
||||
);
|
||||
return track!;
|
||||
}).toList();
|
||||
return sources
|
||||
.map((source) {
|
||||
final track = tracks.firstWhereOrNull(
|
||||
(track) => makeAppropriateSource(track) == source,
|
||||
);
|
||||
return track;
|
||||
})
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// This method must be called after any playback operation as
|
||||
|
@ -56,50 +56,70 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
() async {
|
||||
notificationService = await AudioServices.create(ref, this);
|
||||
|
||||
audioPlayer.currentIndexChangedStream.listen((index) async {
|
||||
if (index == -1 || index == state.active) return;
|
||||
audioPlayer.activeSourceChangedStream.listen((newActiveSource) {
|
||||
final newActiveTrack =
|
||||
mapSourcesToTracks([newActiveSource]).firstOrNull;
|
||||
|
||||
final newIndexedTrack =
|
||||
mapSourcesToTracks([audioPlayer.sources[index]]).firstOrNull;
|
||||
if (newActiveTrack == null ||
|
||||
newActiveTrack.id == state.activeTrack?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newIndexedTrack == null) return;
|
||||
notificationService.addTrack(newIndexedTrack);
|
||||
print('=============== Active Track Changed ===============');
|
||||
|
||||
print('Current tracks: ${state.tracks.map((e) => e.name).toList()}');
|
||||
print('newIndexedTrack: ${newActiveTrack.name}');
|
||||
|
||||
notificationService.addTrack(newActiveTrack);
|
||||
state = state.copyWith(
|
||||
active: state.tracks
|
||||
.toList()
|
||||
.indexWhere((element) => element.id == newIndexedTrack.id),
|
||||
.indexWhere((element) => element.id == newActiveTrack.id),
|
||||
);
|
||||
print('New active: ${state.active}');
|
||||
|
||||
print('=============== ----- ===============');
|
||||
if (preferences.albumColorSync) {
|
||||
updatePalette();
|
||||
}
|
||||
});
|
||||
|
||||
audioPlayer.shuffledStream.listen((event) {
|
||||
print('=============== Shuffled ===============');
|
||||
|
||||
print('oldTracks: ${state.tracks.map((e) => e.name).toList()}');
|
||||
|
||||
final newlyOrderedTracks = mapSourcesToTracks(audioPlayer.sources);
|
||||
final newIndex = newlyOrderedTracks.indexWhere(
|
||||
|
||||
print(
|
||||
'newlyOrderedTracks: ${newlyOrderedTracks.map((e) => e.name).toList()}');
|
||||
|
||||
final newActiveIndex = newlyOrderedTracks.indexWhere(
|
||||
(element) => element.id == state.activeTrack?.id,
|
||||
);
|
||||
|
||||
if (newIndex == -1) return;
|
||||
print('newActiveIndex $newActiveIndex');
|
||||
|
||||
print('=============== ----- ===============');
|
||||
|
||||
if (newActiveIndex == -1) return;
|
||||
|
||||
state = state.copyWith(
|
||||
tracks: newlyOrderedTracks.toSet(),
|
||||
active: newIndex,
|
||||
active: newActiveIndex,
|
||||
);
|
||||
});
|
||||
|
||||
bool isPreSearching = false;
|
||||
audioPlayer.percentCompletedStream(60).listen((percent) async {
|
||||
if (isPreSearching) return;
|
||||
if (isPreSearching || audioPlayer.currentSource == null) return;
|
||||
try {
|
||||
isPreSearching = true;
|
||||
|
||||
// TODO: Make repeat mode sensitive changes later
|
||||
final oldTrack =
|
||||
state.tracks.elementAtOrNull(audioPlayer.currentIndex);
|
||||
final track =
|
||||
await ensureNthSourcePlayable(audioPlayer.currentIndex + 1);
|
||||
mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull;
|
||||
final track = await ensureSourcePlayable(audioPlayer.nextSource!);
|
||||
|
||||
if (track != null) {
|
||||
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
|
||||
@ -129,36 +149,35 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
|
||||
// player stops at 99% if nextSource is still not playable
|
||||
audioPlayer.percentCompletedStream(99).listen((_) async {
|
||||
final nextSource =
|
||||
audioPlayer.sources.elementAtOrNull(audioPlayer.currentIndex + 1);
|
||||
if (nextSource == null || isPlayable(nextSource)) return;
|
||||
if (audioPlayer.nextSource == null ||
|
||||
isPlayable(audioPlayer.nextSource!)) return;
|
||||
await audioPlayer.pause();
|
||||
});
|
||||
}();
|
||||
}
|
||||
|
||||
Future<SpotubeTrack?> ensureNthSourcePlayable(int n) async {
|
||||
final sources = audioPlayer.sources;
|
||||
if (n < 0 || n > sources.length - 1) return null;
|
||||
final nthSource = sources.elementAtOrNull(n);
|
||||
if (nthSource == null || !isUnPlayable(nthSource)) return null;
|
||||
Future<SpotubeTrack?> ensureSourcePlayable(String source) async {
|
||||
print("======== Ensure Source Playable =========");
|
||||
print("source: $source");
|
||||
|
||||
final nthTrack = state.tracks.firstWhereOrNull(
|
||||
(element) => element.id == getIdFromUnPlayable(nthSource),
|
||||
);
|
||||
if (nthTrack == null || nthTrack is LocalTrack) {
|
||||
if (isPlayable(source)) return null;
|
||||
|
||||
final track = mapSourcesToTracks([source]).firstOrNull;
|
||||
|
||||
print("nthTrack: ${track?.name}");
|
||||
if (track == null || track is LocalTrack) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final nthFetchedTrack = switch (nthTrack.runtimeType) {
|
||||
SpotubeTrack => nthTrack as SpotubeTrack,
|
||||
_ => await SpotubeTrack.fetchFromTrack(nthTrack, preferences),
|
||||
final nthFetchedTrack = switch (track.runtimeType) {
|
||||
SpotubeTrack => track as SpotubeTrack,
|
||||
_ => await SpotubeTrack.fetchFromTrack(track, preferences),
|
||||
};
|
||||
|
||||
if (nthSource == nthFetchedTrack.ytUri) return null;
|
||||
print("======== ----- =========");
|
||||
|
||||
await audioPlayer.replaceSource(
|
||||
nthSource,
|
||||
source,
|
||||
nthFetchedTrack.ytUri,
|
||||
);
|
||||
|
||||
@ -235,8 +254,9 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
}
|
||||
|
||||
Future<void> jumpTo(int index) async {
|
||||
final oldTrack = state.tracks.elementAtOrNull(audioPlayer.currentIndex);
|
||||
final track = await ensureNthSourcePlayable(index);
|
||||
final oldTrack =
|
||||
mapSourcesToTracks([audioPlayer.currentSource!]).firstOrNull;
|
||||
final track = await ensureSourcePlayable(audioPlayer.sources[index]);
|
||||
if (track != null) {
|
||||
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
|
||||
}
|
||||
@ -278,8 +298,9 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
Future<void> swapSibling(PipedSearchItem video) async {}
|
||||
|
||||
Future<void> next() async {
|
||||
final oldTrack = state.tracks.elementAtOrNull(audioPlayer.currentIndex + 1);
|
||||
final track = await ensureNthSourcePlayable(audioPlayer.currentIndex + 1);
|
||||
if (audioPlayer.nextSource == null) return;
|
||||
final oldTrack = mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull;
|
||||
final track = await ensureSourcePlayable(audioPlayer.nextSource!);
|
||||
if (track != null) {
|
||||
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
|
||||
}
|
||||
@ -294,8 +315,10 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
}
|
||||
|
||||
Future<void> previous() async {
|
||||
final oldTrack = state.tracks.elementAtOrNull(audioPlayer.currentIndex - 1);
|
||||
final track = await ensureNthSourcePlayable(audioPlayer.currentIndex - 1);
|
||||
if (audioPlayer.previousSource == null) return;
|
||||
final oldTrack =
|
||||
mapSourcesToTracks([audioPlayer.previousSource!]).firstOrNull;
|
||||
final track = await ensureSourcePlayable(audioPlayer.previousSource!);
|
||||
if (track != null) {
|
||||
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
|
||||
}
|
||||
@ -317,9 +340,7 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
return Future.microtask(() async {
|
||||
final activeTrack = state.tracks.firstWhereOrNull(
|
||||
(track) =>
|
||||
track is SpotubeTrack &&
|
||||
track.ytUri ==
|
||||
audioPlayer.sources.elementAtOrNull(audioPlayer.currentIndex),
|
||||
track is SpotubeTrack && track.ytUri == audioPlayer.currentSource,
|
||||
);
|
||||
|
||||
if (activeTrack == null) return;
|
||||
|
@ -62,6 +62,8 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
|
||||
Future<void> stop() async {
|
||||
await _mkPlayer?.stop();
|
||||
await _justAudio?.stop();
|
||||
await _justAudio?.setShuffleModeEnabled(false);
|
||||
await _justAudio?.setLoopMode(ja.LoopMode.off);
|
||||
}
|
||||
|
||||
Future<void> seek(Duration position) async {
|
||||
@ -138,11 +140,45 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
|
||||
}
|
||||
}
|
||||
|
||||
int get currentIndex {
|
||||
String? get currentSource {
|
||||
if (mkSupportedPlatform) {
|
||||
return _mkPlayer!.playlist.index;
|
||||
return _mkPlayer!.playlist.medias
|
||||
.elementAtOrNull(_mkPlayer!.playlist.index)
|
||||
?.uri;
|
||||
} else {
|
||||
return _justAudio!.sequenceState?.currentIndex ?? -1;
|
||||
return (_justAudio?.sequenceState?.effectiveSequence
|
||||
.elementAtOrNull(_justAudio!.sequenceState!.currentIndex)
|
||||
as ja.UriAudioSource?)
|
||||
?.uri
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
String? get nextSource {
|
||||
if (mkSupportedPlatform) {
|
||||
return _mkPlayer!.playlist.medias
|
||||
.elementAtOrNull(_mkPlayer!.playlist.index + 1)
|
||||
?.uri;
|
||||
} else {
|
||||
return (_justAudio?.sequenceState?.effectiveSequence
|
||||
.elementAtOrNull(_justAudio!.sequenceState!.currentIndex + 1)
|
||||
as ja.UriAudioSource?)
|
||||
?.uri
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
String? get previousSource {
|
||||
if (mkSupportedPlatform) {
|
||||
return _mkPlayer!.playlist.medias
|
||||
.elementAtOrNull(_mkPlayer!.playlist.index - 1)
|
||||
?.uri;
|
||||
} else {
|
||||
return (_justAudio?.sequenceState?.effectiveSequence
|
||||
.elementAtOrNull(_justAudio!.sequenceState!.currentIndex - 1)
|
||||
as ja.UriAudioSource?)
|
||||
?.uri
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,21 +245,27 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
|
||||
if (mkSupportedPlatform) {
|
||||
_mkPlayer!.replace(oldSource, newSource);
|
||||
} else {
|
||||
await addTrack(newSource);
|
||||
await removeTrack(oldSourceIndex);
|
||||
final playlist = _justAudio!.audioSource as ja.ConcatenatingAudioSource;
|
||||
|
||||
int newSourceIndex = sources.indexOf(newSource);
|
||||
while (newSourceIndex == -1) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
newSourceIndex = sources.indexOf(newSource);
|
||||
}
|
||||
await moveTrack(newSourceIndex, oldSourceIndex);
|
||||
newSourceIndex = sources.indexOf(newSource);
|
||||
while (newSourceIndex != oldSourceIndex) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await moveTrack(newSourceIndex, oldSourceIndex);
|
||||
newSourceIndex = sources.indexOf(newSource);
|
||||
print('oldSource: $oldSource');
|
||||
print('newSource: $newSource');
|
||||
final oldSourceIndexInPlaylist =
|
||||
_justAudio?.sequenceState?.effectiveSequence.indexWhere(
|
||||
(e) => (e as ja.UriAudioSource).uri.toString() == oldSource,
|
||||
);
|
||||
|
||||
print('oldSourceIndexInPlaylist: $oldSourceIndexInPlaylist');
|
||||
|
||||
// ignores non existing source
|
||||
if (oldSourceIndexInPlaylist == null || oldSourceIndexInPlaylist == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
await playlist.removeAt(oldSourceIndexInPlaylist);
|
||||
await playlist.insert(
|
||||
oldSourceIndexInPlaylist,
|
||||
ja.AudioSource.uri(Uri.parse(newSource)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,4 +127,22 @@ mixin SpotubeAudioPlayersStreams on AudioPlayerInterface {
|
||||
.asBroadcastStream();
|
||||
}
|
||||
}
|
||||
|
||||
Stream<String> get activeSourceChangedStream {
|
||||
if (mkSupportedPlatform) {
|
||||
return _mkPlayer!.indexChangeStream
|
||||
.map((event) {
|
||||
return _mkPlayer!.playlist.medias.elementAtOrNull(event)?.uri;
|
||||
})
|
||||
.where((event) => event != null)
|
||||
.cast<String>();
|
||||
} else {
|
||||
return _justAudio!.sequenceStateStream
|
||||
.map((event) {
|
||||
return (event?.currentSource as ja.UriAudioSource?)?.uri.toString();
|
||||
})
|
||||
.where((event) => event != null)
|
||||
.cast<String>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user