mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
fix(mkPlayer): remove method and wrong active index on modifying playlist
This commit is contained in:
parent
eaf65b6db2
commit
3bafa7b80c
@ -28,10 +28,12 @@ enum SpotubeTrackMatchAlgorithm {
|
|||||||
authenticPopular,
|
authenticPopular,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef SkipSegment = ({int start, int end});
|
||||||
|
|
||||||
class SpotubeTrack extends Track {
|
class SpotubeTrack extends Track {
|
||||||
final PipedStreamResponse ytTrack;
|
final PipedStreamResponse ytTrack;
|
||||||
final String ytUri;
|
final String ytUri;
|
||||||
final List<Map<String, int>> skipSegments;
|
final List<SkipSegment> skipSegments;
|
||||||
final List<PipedSearchItemStream> siblings;
|
final List<PipedSearchItemStream> siblings;
|
||||||
|
|
||||||
SpotubeTrack(
|
SpotubeTrack(
|
||||||
@ -82,7 +84,7 @@ class SpotubeTrack extends Track {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<Map<String, int>>> getSkipSegments(
|
static Future<List<SkipSegment>> getSkipSegments(
|
||||||
String id,
|
String id,
|
||||||
UserPreferences preferences,
|
UserPreferences preferences,
|
||||||
) async {
|
) async {
|
||||||
@ -107,23 +109,23 @@ class SpotubeTrack extends Track {
|
|||||||
));
|
));
|
||||||
|
|
||||||
if (res.body == "Not Found") {
|
if (res.body == "Not Found") {
|
||||||
return List.castFrom<dynamic, Map<String, int>>([]);
|
return List.castFrom<dynamic, SkipSegment>([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
final data = jsonDecode(res.body) as List;
|
final data = jsonDecode(res.body) as List;
|
||||||
final segments = data.map((obj) {
|
final segments = data.map((obj) {
|
||||||
return Map.castFrom<String, dynamic, String, int>({
|
return (
|
||||||
"start": obj["segment"].first.toInt(),
|
start: obj["segment"].first.toInt(),
|
||||||
"end": obj["segment"].last.toInt(),
|
end: obj["segment"].last.toInt(),
|
||||||
});
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
getLogger(SpotubeTrack).v(
|
getLogger(SpotubeTrack).v(
|
||||||
"[SponsorBlock] successfully fetched skip segments for $id",
|
"[SponsorBlock] successfully fetched skip segments for $id",
|
||||||
);
|
);
|
||||||
return List.castFrom<dynamic, Map<String, int>>(segments);
|
return List.castFrom<dynamic, SkipSegment>(segments);
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
Catcher.reportCheckedError(e, stack);
|
Catcher.reportCheckedError(e, stack);
|
||||||
return List.castFrom<dynamic, Map<String, int>>([]);
|
return List.castFrom<dynamic, SkipSegment>([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +157,7 @@ class SpotubeTrack extends Track {
|
|||||||
PipedStreamResponse ytVideo;
|
PipedStreamResponse ytVideo;
|
||||||
PipedAudioStream ytStream;
|
PipedAudioStream ytStream;
|
||||||
List<PipedSearchItemStream> siblings = [];
|
List<PipedSearchItemStream> siblings = [];
|
||||||
List<Map<String, int>> skipSegments = [];
|
List<SkipSegment> skipSegments = [];
|
||||||
if (cachedTrack != null) {
|
if (cachedTrack != null) {
|
||||||
final responses = await Future.wait(
|
final responses = await Future.wait(
|
||||||
[
|
[
|
||||||
@ -167,7 +169,7 @@ class SpotubeTrack extends Track {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
ytVideo = responses.first as PipedStreamResponse;
|
ytVideo = responses.first as PipedStreamResponse;
|
||||||
skipSegments = responses.last as List<Map<String, int>>;
|
skipSegments = responses.last as List<SkipSegment>;
|
||||||
ytStream = getStreamInfo(ytVideo, preferences.audioQuality);
|
ytStream = getStreamInfo(ytVideo, preferences.audioQuality);
|
||||||
} else {
|
} else {
|
||||||
final videos = await PipedSpotube.client
|
final videos = await PipedSpotube.client
|
||||||
@ -187,7 +189,7 @@ class SpotubeTrack extends Track {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
ytVideo = responses.first as PipedStreamResponse;
|
ytVideo = responses.first as PipedStreamResponse;
|
||||||
skipSegments = responses.last as List<Map<String, int>>;
|
skipSegments = responses.last as List<SkipSegment>;
|
||||||
ytStream = getStreamInfo(ytVideo, preferences.audioQuality);
|
ytStream = getStreamInfo(ytVideo, preferences.audioQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +241,7 @@ class SpotubeTrack extends Track {
|
|||||||
) async {
|
) async {
|
||||||
if (siblings.none((element) => element.id == video.id)) return null;
|
if (siblings.none((element) => element.id == video.id)) return null;
|
||||||
|
|
||||||
final [PipedStreamResponse ytVideo, List<Map<String, int>> skipSegments] =
|
final [PipedStreamResponse ytVideo, List<SkipSegment> skipSegments] =
|
||||||
await Future.wait<dynamic>(
|
await Future.wait<dynamic>(
|
||||||
[
|
[
|
||||||
PipedSpotube.client.streams(video.id),
|
PipedSpotube.client.streams(video.id),
|
||||||
@ -329,8 +331,7 @@ class SpotubeTrack extends Track {
|
|||||||
track: Track.fromJson(map),
|
track: Track.fromJson(map),
|
||||||
ytTrack: PipedStreamResponse.fromJson(map["ytTrack"]),
|
ytTrack: PipedStreamResponse.fromJson(map["ytTrack"]),
|
||||||
ytUri: map["ytUri"],
|
ytUri: map["ytUri"],
|
||||||
skipSegments:
|
skipSegments: List.castFrom<dynamic, SkipSegment>(map["skipSegments"]),
|
||||||
List.castFrom<dynamic, Map<String, int>>(map["skipSegments"]),
|
|
||||||
siblings: List.castFrom<dynamic, Map<String, dynamic>>(map["siblings"])
|
siblings: List.castFrom<dynamic, Map<String, dynamic>>(map["siblings"])
|
||||||
.map((sibling) => PipedSearchItemStream.fromJson(sibling))
|
.map((sibling) => PipedSearchItemStream.fromJson(sibling))
|
||||||
.toList(),
|
.toList(),
|
||||||
|
@ -57,6 +57,8 @@ mixin NextFetcher on StateNotifier<ProxyPlaylist> {
|
|||||||
return source.startsWith('https://youtube.com/unplayable.m4a?id=');
|
return source.startsWith('https://youtube.com/unplayable.m4a?id=');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isPlayable(String source) => !isUnPlayable(source);
|
||||||
|
|
||||||
/// Returns [Track.id] from [isUnPlayable] source that is not playable
|
/// Returns [Track.id] from [isUnPlayable] source that is not playable
|
||||||
String getIdFromUnPlayable(String source) {
|
String getIdFromUnPlayable(String source) {
|
||||||
return source.replaceFirst('https://youtube.com/unplayable.m4a?id=', '');
|
return source.replaceFirst('https://youtube.com/unplayable.m4a?id=', '');
|
||||||
|
@ -101,7 +101,7 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
|||||||
audioPlayer.percentCompletedStream(99).listen((_) async {
|
audioPlayer.percentCompletedStream(99).listen((_) async {
|
||||||
final nextSource =
|
final nextSource =
|
||||||
audioPlayer.sources.elementAtOrNull(audioPlayer.currentIndex + 1);
|
audioPlayer.sources.elementAtOrNull(audioPlayer.currentIndex + 1);
|
||||||
if (nextSource == null || !isUnPlayable(nextSource)) return;
|
if (nextSource == null || isPlayable(nextSource)) return;
|
||||||
await audioPlayer.pause();
|
await audioPlayer.pause();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -118,20 +118,17 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
|||||||
if (activeTrack.skipSegments.isNotEmpty == true &&
|
if (activeTrack.skipSegments.isNotEmpty == true &&
|
||||||
preferences.skipSponsorSegments) {
|
preferences.skipSponsorSegments) {
|
||||||
for (final segment in activeTrack.skipSegments) {
|
for (final segment in activeTrack.skipSegments) {
|
||||||
if (pos.inSeconds < segment["start"]! ||
|
if (pos.inSeconds < segment.start || pos.inSeconds >= segment.end) {
|
||||||
pos.inSeconds >= segment["end"]!) continue;
|
continue;
|
||||||
await audioPlayer.seek(Duration(seconds: segment["end"]!));
|
}
|
||||||
|
await audioPlayer.seek(Duration(seconds: segment.end));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}();
|
}();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SpotubeTrack?> ensureNthSourcePlayable(
|
Future<SpotubeTrack?> ensureNthSourcePlayable(int n) async {
|
||||||
int n, {
|
|
||||||
bool softReplace = false,
|
|
||||||
bool exclusive = false,
|
|
||||||
}) async {
|
|
||||||
final sources = audioPlayer.sources;
|
final sources = audioPlayer.sources;
|
||||||
if (n < 0 || n > sources.length - 1) return null;
|
if (n < 0 || n > sources.length - 1) return null;
|
||||||
final nthSource = sources.elementAtOrNull(n);
|
final nthSource = sources.elementAtOrNull(n);
|
||||||
@ -150,13 +147,10 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
|||||||
|
|
||||||
if (nthSource == nthFetchedTrack.ytUri) return null;
|
if (nthSource == nthFetchedTrack.ytUri) return null;
|
||||||
|
|
||||||
if (!softReplace) {
|
await audioPlayer.replaceSource(
|
||||||
await audioPlayer.replaceSource(
|
nthSource,
|
||||||
nthSource,
|
nthFetchedTrack.ytUri,
|
||||||
nthFetchedTrack.ytUri,
|
);
|
||||||
exclusive: exclusive,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nthFetchedTrack;
|
return nthFetchedTrack;
|
||||||
}
|
}
|
||||||
@ -214,7 +208,9 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
|||||||
await SpotubeTrack.fetchFromTrack(tracks[initialIndex], preferences);
|
await SpotubeTrack.fetchFromTrack(tracks[initialIndex], preferences);
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
tracks: mergeTracks([addableTrack], tracks), active: initialIndex);
|
tracks: mergeTracks([addableTrack], tracks),
|
||||||
|
active: initialIndex,
|
||||||
|
);
|
||||||
|
|
||||||
await audioPlayer.openPlaylist(
|
await audioPlayer.openPlaylist(
|
||||||
state.tracks.map(makeAppropriateSource).toList(),
|
state.tracks.map(makeAppropriateSource).toList(),
|
||||||
|
@ -458,35 +458,25 @@ class SpotubeAudioPlayer {
|
|||||||
final oldSourceIndex = sources.indexOf(oldSource);
|
final oldSourceIndex = sources.indexOf(oldSource);
|
||||||
if (oldSourceIndex == -1) return;
|
if (oldSourceIndex == -1) return;
|
||||||
|
|
||||||
// if (mkSupportedPlatform) {
|
if (mkSupportedPlatform) {
|
||||||
// final sourcesCp = sources.toList();
|
_mkPlayer!.replace(oldSource, newSource);
|
||||||
// sourcesCp[oldSourceIndex] = newSource;
|
} else {
|
||||||
|
await addTrack(newSource);
|
||||||
|
await removeTrack(oldSourceIndex);
|
||||||
|
|
||||||
// await _mkPlayer!.open(
|
int newSourceIndex = sources.indexOf(newSource);
|
||||||
// mk.Playlist(
|
while (newSourceIndex == -1) {
|
||||||
// sourcesCp.map(mk.Media.new).toList(),
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
// index: currentIndex,
|
newSourceIndex = sources.indexOf(newSource);
|
||||||
// ),
|
}
|
||||||
// play: false,
|
|
||||||
// );
|
|
||||||
// if (exclusive) await jumpTo(oldSourceIndex);
|
|
||||||
// } else {
|
|
||||||
await addTrack(newSource);
|
|
||||||
await removeTrack(oldSourceIndex);
|
|
||||||
|
|
||||||
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);
|
await moveTrack(newSourceIndex, oldSourceIndex);
|
||||||
newSourceIndex = sources.indexOf(newSource);
|
newSourceIndex = sources.indexOf(newSource);
|
||||||
|
while (newSourceIndex != oldSourceIndex) {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
await moveTrack(newSourceIndex, oldSourceIndex);
|
||||||
|
newSourceIndex = sources.indexOf(newSource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearPlaylist() async {
|
Future<void> clearPlaylist() async {
|
||||||
|
@ -67,10 +67,10 @@ class MkPlayerWithState extends Player {
|
|||||||
Stream<PlaylistMode> get loopModeStream => _loopModeStream.stream;
|
Stream<PlaylistMode> get loopModeStream => _loopModeStream.stream;
|
||||||
Stream<Playlist> get playlistStream => _playlistStream.stream;
|
Stream<Playlist> get playlistStream => _playlistStream.stream;
|
||||||
Stream<int> get indexChangeStream {
|
Stream<int> get indexChangeStream {
|
||||||
int index = playlist.index;
|
int oldIndex = playlist.index;
|
||||||
return playlistStream.map((event) => event.index).where((event) {
|
return playlistStream.map((event) => event.index).where((newIndex) {
|
||||||
if (event != index) {
|
if (newIndex != oldIndex) {
|
||||||
index = event;
|
oldIndex = newIndex;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -116,16 +116,13 @@ class MkPlayerWithState extends Player {
|
|||||||
|
|
||||||
Future<void> stop() async {
|
Future<void> stop() async {
|
||||||
await pause();
|
await pause();
|
||||||
|
await seek(Duration.zero);
|
||||||
|
|
||||||
_loopMode = PlaylistMode.none;
|
_loopMode = PlaylistMode.none;
|
||||||
_shuffled = false;
|
_shuffled = false;
|
||||||
_playlist = null;
|
_playlist = null;
|
||||||
_tempMedias = null;
|
_tempMedias = null;
|
||||||
_playerStateStream.add(AudioPlaybackState.stopped);
|
_playerStateStream.add(AudioPlaybackState.stopped);
|
||||||
|
|
||||||
for (int i = 0; i < state.playlist.medias.length; i++) {
|
|
||||||
await remove(i);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -141,6 +138,7 @@ class MkPlayerWithState extends Player {
|
|||||||
Playable playable, {
|
Playable playable, {
|
||||||
bool play = true,
|
bool play = true,
|
||||||
}) async {
|
}) async {
|
||||||
|
await stop();
|
||||||
if (playable is Playlist) {
|
if (playable is Playlist) {
|
||||||
playlist = playable;
|
playlist = playable;
|
||||||
super.open(playable.medias[playable.index], play: play);
|
super.open(playable.medias[playable.index], play: play);
|
||||||
@ -154,14 +152,15 @@ class MkPlayerWithState extends Player {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loopMode == PlaylistMode.loop &&
|
final isLast = _playlist!.index == _playlist!.medias.length - 1;
|
||||||
_playlist!.index == _playlist!.medias.length - 1) {
|
|
||||||
playlist = _playlist!.copyWith(index: 0);
|
|
||||||
} else {
|
|
||||||
playlist = _playlist!.copyWith(index: _playlist!.index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.open(_playlist!.medias[_playlist!.index], play: true);
|
if (loopMode == PlaylistMode.loop && isLast) {
|
||||||
|
playlist = _playlist!.copyWith(index: 0);
|
||||||
|
return super.open(_playlist!.medias[_playlist!.index], play: true);
|
||||||
|
} else if (!isLast) {
|
||||||
|
playlist = _playlist!.copyWith(index: _playlist!.index + 1);
|
||||||
|
return super.open(_playlist!.medias[_playlist!.index], play: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -170,11 +169,11 @@ class MkPlayerWithState extends Player {
|
|||||||
|
|
||||||
if (loopMode == PlaylistMode.loop && _playlist!.index == 0) {
|
if (loopMode == PlaylistMode.loop && _playlist!.index == 0) {
|
||||||
playlist = _playlist!.copyWith(index: _playlist!.medias.length - 1);
|
playlist = _playlist!.copyWith(index: _playlist!.medias.length - 1);
|
||||||
} else {
|
return super.open(_playlist!.medias[_playlist!.index], play: true);
|
||||||
|
} else if (_playlist!.index != 0) {
|
||||||
playlist = _playlist!.copyWith(index: _playlist!.index - 1);
|
playlist = _playlist!.copyWith(index: _playlist!.index - 1);
|
||||||
|
return super.open(_playlist!.medias[_playlist!.index], play: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.open(_playlist!.medias[_playlist!.index], play: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -184,7 +183,7 @@ class MkPlayerWithState extends Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
playlist = _playlist!.copyWith(index: index);
|
playlist = _playlist!.copyWith(index: index);
|
||||||
return super.open(_playlist!.medias[_playlist!.index], play: true);
|
return super.open(_playlist!.medias[index], play: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -210,6 +209,27 @@ class MkPlayerWithState extends Player {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This replaces the old source with a new one
|
||||||
|
///
|
||||||
|
/// This doesn't work when [playlist] is null
|
||||||
|
/// Or, when the current media is the one to be replaced
|
||||||
|
void replace(String oldUrl, String newUrl) {
|
||||||
|
if (_playlist == null ||
|
||||||
|
_playlist!.medias[_playlist!.index].uri == oldUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _playlist!.medias.length - 1; i++) {
|
||||||
|
final media = _playlist!.medias[i];
|
||||||
|
if (media.uri == oldUrl) {
|
||||||
|
final newMedias = _playlist!.medias.toList();
|
||||||
|
newMedias[i] = Media(newUrl, extras: media.extras);
|
||||||
|
playlist = _playlist!.copyWith(medias: newMedias);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<void> add(Media media) {
|
FutureOr<void> add(Media media) {
|
||||||
if (_playlist == null) return null;
|
if (_playlist == null) return null;
|
||||||
@ -223,45 +243,28 @@ class MkPlayerWithState extends Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Doesn't work when active media is the one to be removed
|
||||||
@override
|
@override
|
||||||
FutureOr<void> remove(int index) async {
|
FutureOr<void> remove(int index) async {
|
||||||
if (_playlist == null || index >= _playlist!.medias.length) return null;
|
if (_playlist == null ||
|
||||||
|
index < 0 ||
|
||||||
final item = _playlist!.medias.elementAtOrNull(index);
|
index > _playlist!.medias.length - 1 ||
|
||||||
if (shuffled && _tempMedias != null && item != null) {
|
_playlist!.index == index) {
|
||||||
_tempMedias!.remove(item);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_playlist!.index == index) {
|
final targetItem = _playlist!.medias.elementAtOrNull(index);
|
||||||
final hasNext = _playlist!.index + 1 < _playlist!.medias.length;
|
if (targetItem == null) return null;
|
||||||
final hasPrevious = _playlist!.index - 1 >= 0;
|
|
||||||
|
|
||||||
if (hasNext) {
|
if (shuffled && _tempMedias != null) {
|
||||||
playlist = _playlist!.copyWith(
|
_tempMedias!.remove(targetItem);
|
||||||
index: _playlist!.index + 1,
|
|
||||||
medias: _playlist!.medias..removeAt(index),
|
|
||||||
);
|
|
||||||
super.open(_playlist!.medias[_playlist!.index], play: true);
|
|
||||||
} else if (hasPrevious) {
|
|
||||||
playlist = _playlist!.copyWith(
|
|
||||||
index: _playlist!.index - 1,
|
|
||||||
medias: _playlist!.medias..removeAt(index),
|
|
||||||
);
|
|
||||||
super.open(_playlist!.medias[_playlist!.index], play: true);
|
|
||||||
} else {
|
|
||||||
playlist = _playlist!.copyWith(
|
|
||||||
medias: _playlist!.medias..removeAt(index),
|
|
||||||
index: -1,
|
|
||||||
);
|
|
||||||
await stop();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final active = _playlist!.medias[_playlist!.index];
|
|
||||||
final newMedias = _playlist!.medias..removeAt(index);
|
|
||||||
playlist = _playlist!.copyWith(
|
|
||||||
medias: newMedias,
|
|
||||||
index: newMedias.indexOf(active),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final newMedias = _playlist!.medias.toList()..removeAt(index);
|
||||||
|
|
||||||
|
playlist = _playlist!.copyWith(
|
||||||
|
medias: newMedias,
|
||||||
|
index: newMedias.indexOf(_playlist!.medias[_playlist!.index]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user