Skip to content

Commit

Permalink
fix: use id based source getters instead of index
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed May 27, 2023
1 parent 75b46e1 commit a074463
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 64 deletions.
25 changes: 17 additions & 8 deletions lib/provider/proxy_playlist/next_fetcher_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down
101 changes: 61 additions & 40 deletions lib/provider/proxy_playlist/proxy_playlist_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

print('=============== Active Track Changed ===============');

print('Current tracks: ${state.tracks.map((e) => e.name).toList()}');
print('newIndexedTrack: ${newActiveTrack.name}');

if (newIndexedTrack == null) return;
notificationService.addTrack(newIndexedTrack);
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));
Expand Down Expand Up @@ -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,
);

Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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));
}
Expand All @@ -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));
}
Expand All @@ -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;
Expand Down
74 changes: 58 additions & 16 deletions lib/services/audio_player/audio_player_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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)),
);
}
}

Expand Down
18 changes: 18 additions & 0 deletions lib/services/audio_player/audio_players_streams_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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>();
}
}
}

0 comments on commit a074463

Please sign in to comment.