mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 16:05:18 +00:00
fix(player): volume slider, prefetching of media_kit and stuttering on sponsorblock skip
This commit is contained in:
parent
5f70207076
commit
1f3255481f
@ -20,6 +20,9 @@ import 'package:youtube_explode_dart/youtube_explode_dart.dart';
|
||||
/// * [x] Prefetch next track as [SpotubeTrack] on 80% of current track
|
||||
/// * [ ] Mixed Queue containing both [SpotubeTrack] and [LocalTrack]
|
||||
/// * [ ] Caching and loading of cache of tracks
|
||||
/// * [ ] Shuffling and loop => playlist, track, none
|
||||
/// * [ ] Alternative Track Source
|
||||
/// * [x] Blacklisting of tracks and artist
|
||||
///
|
||||
/// Don'ts:
|
||||
/// * It'll not have any proxy method for [SpotubeAudioPlayer]
|
||||
@ -48,7 +51,8 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
notificationService = await AudioServices.create(ref, this);
|
||||
|
||||
audioPlayer.currentIndexChangedStream.listen((index) async {
|
||||
if (index == -1) return;
|
||||
if (index == -1 || index == state.active) return;
|
||||
|
||||
final track = state.tracks.elementAtOrNull(index);
|
||||
if (track == null) return;
|
||||
notificationService.addTrack(track);
|
||||
@ -60,14 +64,26 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
});
|
||||
|
||||
bool isPreSearching = false;
|
||||
audioPlayer.percentCompletedStream(80).listen((_) async {
|
||||
audioPlayer.percentCompletedStream(60).listen((percent) async {
|
||||
if (isPreSearching) return;
|
||||
try {
|
||||
isPreSearching = true;
|
||||
|
||||
final softReplace =
|
||||
SpotubeAudioPlayer.mkSupportedPlatform && percent <= 98;
|
||||
|
||||
// TODO: Make repeat mode sensitive changes later
|
||||
final track =
|
||||
await ensureNthSourcePlayable(audioPlayer.currentIndex + 1);
|
||||
final track = await ensureNthSourcePlayable(
|
||||
audioPlayer.currentIndex + 1,
|
||||
|
||||
/// [MediaKit] doesn't fully support replacing source, so we need
|
||||
/// to check if the platform is supported or not and replace the
|
||||
/// actual playlist with a playlist that contains the next track
|
||||
/// at 98% >= progress
|
||||
softReplace: softReplace,
|
||||
exclusive: SpotubeAudioPlayer.mkSupportedPlatform,
|
||||
);
|
||||
|
||||
if (track != null) {
|
||||
state = state.copyWith(tracks: mergeTracks([track], state.tracks));
|
||||
}
|
||||
@ -109,8 +125,7 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
preferences.skipSponsorSegments) {
|
||||
for (final segment in activeTrack.skipSegments) {
|
||||
if (pos.inSeconds < segment["start"]! ||
|
||||
pos.inSeconds > segment["end"]!) continue;
|
||||
await audioPlayer.pause();
|
||||
pos.inSeconds >= segment["end"]!) continue;
|
||||
await audioPlayer.seek(Duration(seconds: segment["end"]!));
|
||||
}
|
||||
}
|
||||
@ -118,7 +133,11 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
}();
|
||||
}
|
||||
|
||||
Future<SpotubeTrack?> ensureNthSourcePlayable(int n) async {
|
||||
Future<SpotubeTrack?> ensureNthSourcePlayable(
|
||||
int n, {
|
||||
bool softReplace = false,
|
||||
bool exclusive = false,
|
||||
}) async {
|
||||
final sources = audioPlayer.sources;
|
||||
if (n < 0 || n > sources.length - 1) return null;
|
||||
final nthSource = sources.elementAtOrNull(n);
|
||||
@ -127,19 +146,23 @@ class ProxyPlaylistNotifier extends StateNotifier<ProxyPlaylist>
|
||||
final nthTrack = state.tracks.firstWhereOrNull(
|
||||
(element) => element.id == getIdFromUnPlayable(nthSource),
|
||||
);
|
||||
if (nthTrack == null ||
|
||||
nthTrack is SpotubeTrack ||
|
||||
nthTrack is LocalTrack) {
|
||||
if (nthTrack == null || nthTrack is LocalTrack) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final nthFetchedTrack =
|
||||
await SpotubeTrack.fetchFromTrack(nthTrack, preferences);
|
||||
final nthFetchedTrack = nthTrack is SpotubeTrack
|
||||
? nthTrack
|
||||
: await SpotubeTrack.fetchFromTrack(nthTrack, preferences);
|
||||
|
||||
if (nthSource == nthFetchedTrack.ytUri) return null;
|
||||
|
||||
if (!softReplace) {
|
||||
await audioPlayer.replaceSource(
|
||||
nthSource,
|
||||
nthFetchedTrack.ytUri,
|
||||
exclusive: exclusive,
|
||||
);
|
||||
}
|
||||
|
||||
return nthFetchedTrack;
|
||||
}
|
||||
|
@ -64,15 +64,16 @@ class SpotubeAudioPlayer {
|
||||
}
|
||||
|
||||
/// Stream that emits when the player is almost (%) complete
|
||||
Stream<void> percentCompletedStream(double percent) {
|
||||
Stream<int> percentCompletedStream(double percent) {
|
||||
return positionStream
|
||||
.asyncMap((event) async => [event, await duration])
|
||||
.where((event) {
|
||||
final position = event[0] as Duration;
|
||||
final duration = event[1] as Duration;
|
||||
|
||||
return position.inSeconds > duration.inSeconds * percent / 100;
|
||||
}).asBroadcastStream();
|
||||
.asyncMap(
|
||||
(position) async => (await duration)?.inSeconds == 0
|
||||
? 0
|
||||
: (position.inSeconds / (await duration)!.inSeconds * 100)
|
||||
.toInt(),
|
||||
)
|
||||
.where((event) => event >= percent)
|
||||
.asBroadcastStream();
|
||||
}
|
||||
|
||||
Stream<bool> get playingStream {
|
||||
@ -143,12 +144,8 @@ class SpotubeAudioPlayer {
|
||||
.map((event) => event.index)
|
||||
.asBroadcastStream();
|
||||
} else {
|
||||
return _justAudio!.positionDiscontinuityStream
|
||||
.where(
|
||||
(event) =>
|
||||
event.reason == ja.PositionDiscontinuityReason.autoAdvance,
|
||||
)
|
||||
.map((event) => currentIndex)
|
||||
return _justAudio!.sequenceStateStream
|
||||
.map((event) => event?.currentIndex ?? -1)
|
||||
.asBroadcastStream();
|
||||
}
|
||||
}
|
||||
@ -237,6 +234,7 @@ class SpotubeAudioPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current volume of the player, between 0 and 1
|
||||
double get volume {
|
||||
if (mkSupportedPlatform) {
|
||||
return _mkPlayer!.state.volume / 100;
|
||||
@ -320,8 +318,10 @@ class SpotubeAudioPlayer {
|
||||
await _justAudio?.seek(position);
|
||||
}
|
||||
|
||||
/// Volume is between 0 and 1
|
||||
Future<void> setVolume(double volume) async {
|
||||
await _mkPlayer?.setVolume(volume);
|
||||
assert(volume >= 0 && volume <= 1);
|
||||
await _mkPlayer?.setVolume(volume * 100);
|
||||
await _justAudio?.setVolume(volume);
|
||||
}
|
||||
|
||||
@ -391,7 +391,7 @@ class SpotubeAudioPlayer {
|
||||
if (mkSupportedPlatform) {
|
||||
return _mkPlayer!.state.playlist.index;
|
||||
} else {
|
||||
return _justAudio!.sequenceState!.currentIndex;
|
||||
return _justAudio!.sequenceState?.currentIndex ?? -1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -447,13 +447,18 @@ class SpotubeAudioPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> replaceSource(String oldSource, String newSource) async {
|
||||
final willBeReplacedIndex = sources.indexOf(oldSource);
|
||||
if (willBeReplacedIndex == -1) return;
|
||||
Future<void> replaceSource(
|
||||
String oldSource,
|
||||
String newSource, {
|
||||
bool exclusive = false,
|
||||
}) async {
|
||||
final oldSourceIndex = sources.indexOf(oldSource);
|
||||
if (oldSourceIndex == -1) return;
|
||||
|
||||
if (mkSupportedPlatform) {
|
||||
final sourcesCp = sources.toList();
|
||||
sourcesCp[willBeReplacedIndex] = newSource;
|
||||
sourcesCp[oldSourceIndex] = newSource;
|
||||
|
||||
await _mkPlayer!.open(
|
||||
mk.Playlist(
|
||||
sourcesCp.map(mk.Media.new).toList(),
|
||||
@ -461,20 +466,21 @@ class SpotubeAudioPlayer {
|
||||
),
|
||||
play: false,
|
||||
);
|
||||
if (exclusive) await jumpTo(oldSourceIndex);
|
||||
} else {
|
||||
await addTrack(newSource);
|
||||
await removeTrack(willBeReplacedIndex);
|
||||
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, willBeReplacedIndex);
|
||||
await moveTrack(newSourceIndex, oldSourceIndex);
|
||||
newSourceIndex = sources.indexOf(newSource);
|
||||
while (newSourceIndex != willBeReplacedIndex) {
|
||||
while (newSourceIndex != oldSourceIndex) {
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
await moveTrack(newSourceIndex, willBeReplacedIndex);
|
||||
await moveTrack(newSourceIndex, oldSourceIndex);
|
||||
newSourceIndex = sources.indexOf(newSource);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user