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